Skip to content
Permalink
Browse files
Added support of <Text>'s selectable attribute on iOS
Reviewed By: mmmulani

Differential Revision: D4187562

fbshipit-source-id: 131ece141fe8b895914043a7a01c6e042e858331
  • Loading branch information
shergin authored and Facebook Github Bot committed Nov 18, 2016
1 parent 2a2ba52 commit 5d03ff8035bdb7c582f80d6ce46d7c5f11545e7e
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 3 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -276,6 +276,17 @@ exports.examples = [
</View>
);
},
}, {
title: 'Selectable',
render: function() {
return (
<View>
<Text selectable={true}>
This text is <Text style={{fontWeight: 'bold'}}>selectable</Text> if you click-and-hold.
</Text>
</View>
);
},
}, {
title: 'Text Decoration',
render: function() {
@@ -48,6 +48,7 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, strong) UIColor *textShadowColor;
@property (nonatomic, assign) BOOL adjustsFontSizeToFit;
@property (nonatomic, assign) CGFloat minimumFontScale;
@property (nonatomic, assign) BOOL selectable;

- (void)recomputeText;

@@ -115,10 +115,12 @@ - (void)contentSizeMultiplierDidChange:(NSNotification *)note
NSNumber *parentTag = [[self reactSuperview] reactTag];
NSTextStorage *textStorage = [self buildTextStorageForWidth:width widthMode:CSSMeasureModeExactly];
CGRect textFrame = [self calculateTextFrame:textStorage];
BOOL selectable = _selectable;
[applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
RCTText *view = (RCTText *)viewRegistry[self.reactTag];
view.textFrame = textFrame;
view.textStorage = textStorage;
view.selectable = selectable;

/**
* NOTE: this logic is included to support rich text editing inside multiline
@@ -14,6 +14,6 @@
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, strong) NSTextStorage *textStorage;
@property (nonatomic, assign) CGRect textFrame;

@property (nonatomic, assign) BOOL selectable;

@end
@@ -9,6 +9,8 @@

#import "RCTText.h"

#import <MobileCoreServices/UTCoreTypes.h>

#import "RCTShadowText.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@@ -28,6 +30,7 @@ @implementation RCTText
{
NSTextStorage *_textStorage;
CAShapeLayer *_highlightLayer;
UILongPressGestureRecognizer *_longPressGestureRecognizer;
}

- (instancetype)initWithFrame:(CGRect)frame
@@ -51,6 +54,22 @@ - (NSString *)description
return [superDescription stringByReplacingCharactersInRange:semicolonRange withString:replacement];
}

- (void)setSelectable:(BOOL)selectable
{
if (_selectable == selectable) {
return;
}

_selectable = selectable;

if (_selectable) {
[self enableContextMenu];
}
else {
[self disableContextMenu];
}
}

- (void)reactSetFrame:(CGRect)frame
{
// Text looks super weird if its frame is animated.
@@ -177,4 +196,72 @@ - (NSString *)accessibilityLabel
return _textStorage.string;
}

#pragma mark - Context Menu

- (void)enableContextMenu
{
_longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
[self addGestureRecognizer:_longPressGestureRecognizer];
}

- (void)disableContextMenu
{
[self removeGestureRecognizer:_longPressGestureRecognizer];
_longPressGestureRecognizer = nil;
}

- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
{
#if !TARGET_OS_TV
UIMenuController *menuController = [UIMenuController sharedMenuController];

if (menuController.isMenuVisible) {
return;
}

if (!self.isFirstResponder) {
[self becomeFirstResponder];
}

[menuController setTargetRect:self.bounds inView:self];
[menuController setMenuVisible:YES animated:YES];
#endif
}

- (BOOL)canBecomeFirstResponder
{
return _selectable;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == @selector(copy:)) {
return YES;
}

return [super canPerformAction:action withSender:sender];
}

- (void)copy:(id)sender
{
#if !TARGET_OS_TV
NSAttributedString *attributedString = _textStorage;

NSMutableDictionary *item = [NSMutableDictionary new];

NSData *rtf = [attributedString dataFromRange:NSMakeRange(0, attributedString.length)
documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType}
error:nil];

if (rtf) {
[item setObject:rtf forKey:(id)kUTTypeFlatRTFD];
}

[item setObject:attributedString.string forKey:(id)kUTTypeUTF8PlainText];

UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.items = @[item];
#endif
}

@end
@@ -84,6 +84,7 @@ - (RCTShadowView *)shadowView
RCT_EXPORT_SHADOW_PROPERTY(textShadowColor, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(adjustsFontSizeToFit, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(minimumFontScale, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(selectable, BOOL)

- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
{
@@ -136,8 +136,6 @@ const Text = React.createClass({
onLongPress: React.PropTypes.func,
/**
* Lets the user select text, to use the native copy and paste functionality.
*
* @platform android
*/
selectable: React.PropTypes.bool,
/**

3 comments on commit 5d03ff8

@messense
Copy link

@messense messense commented on 5d03ff8 Nov 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job!.

@mikelambert
Copy link
Contributor

@mikelambert mikelambert commented on 5d03ff8 Dec 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay! I believe this fixes https://productpains.com/post/react-native/support-selecting-text-within-text-elements-for-copypaste-etc (though someone with admin privileges will have to mark it as fixed, as I don't have that power)

@nihgwu
Copy link
Contributor

@nihgwu nihgwu commented on 5d03ff8 Dec 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikelambert Actually that pain hasn't been fixed, this PR only support copy the text in but you can't select a section of it

Please sign in to comment.