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
Binary file not shown.
Binary file not shown.
@@ -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

This comment has been minimized.

Copy link

@messense messense replied Nov 18, 2016

Great job!.

@mikelambert

This comment has been minimized.

Copy link
Contributor

@mikelambert mikelambert replied Dec 2, 2016

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

This comment has been minimized.

Copy link
Contributor

@nihgwu nihgwu replied Dec 2, 2016

@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.
You can’t perform that action at this time.