Skip to content

Commit 5d03ff8

Browse files
sherginFacebook Github Bot
authored andcommitted
Added support of <Text>'s selectable attribute on iOS
Reviewed By: mmmulani Differential Revision: D4187562 fbshipit-source-id: 131ece141fe8b895914043a7a01c6e042e858331
1 parent 2a2ba52 commit 5d03ff8

File tree

9 files changed

+103
-3
lines changed

9 files changed

+103
-3
lines changed
Loading
557 Bytes
Loading

Examples/UIExplorer/js/TextExample.ios.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,17 @@ exports.examples = [
276276
</View>
277277
);
278278
},
279+
}, {
280+
title: 'Selectable',
281+
render: function() {
282+
return (
283+
<View>
284+
<Text selectable={true}>
285+
This text is <Text style={{fontWeight: 'bold'}}>selectable</Text> if you click-and-hold.
286+
</Text>
287+
</View>
288+
);
289+
},
279290
}, {
280291
title: 'Text Decoration',
281292
render: function() {

Libraries/Text/RCTShadowText.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ extern NSString *const RCTReactTagAttributeName;
4848
@property (nonatomic, strong) UIColor *textShadowColor;
4949
@property (nonatomic, assign) BOOL adjustsFontSizeToFit;
5050
@property (nonatomic, assign) CGFloat minimumFontScale;
51+
@property (nonatomic, assign) BOOL selectable;
5152

5253
- (void)recomputeText;
5354

Libraries/Text/RCTShadowText.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,12 @@ - (void)contentSizeMultiplierDidChange:(NSNotification *)note
115115
NSNumber *parentTag = [[self reactSuperview] reactTag];
116116
NSTextStorage *textStorage = [self buildTextStorageForWidth:width widthMode:CSSMeasureModeExactly];
117117
CGRect textFrame = [self calculateTextFrame:textStorage];
118+
BOOL selectable = _selectable;
118119
[applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
119120
RCTText *view = (RCTText *)viewRegistry[self.reactTag];
120121
view.textFrame = textFrame;
121122
view.textStorage = textStorage;
123+
view.selectable = selectable;
122124

123125
/**
124126
* NOTE: this logic is included to support rich text editing inside multiline

Libraries/Text/RCTText.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
@property (nonatomic, assign) UIEdgeInsets contentInset;
1515
@property (nonatomic, strong) NSTextStorage *textStorage;
1616
@property (nonatomic, assign) CGRect textFrame;
17-
17+
@property (nonatomic, assign) BOOL selectable;
1818

1919
@end

Libraries/Text/RCTText.m

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
#import "RCTText.h"
1111

12+
#import <MobileCoreServices/UTCoreTypes.h>
13+
1214
#import "RCTShadowText.h"
1315
#import "RCTUtils.h"
1416
#import "UIView+React.h"
@@ -28,6 +30,7 @@ @implementation RCTText
2830
{
2931
NSTextStorage *_textStorage;
3032
CAShapeLayer *_highlightLayer;
33+
UILongPressGestureRecognizer *_longPressGestureRecognizer;
3134
}
3235

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

57+
- (void)setSelectable:(BOOL)selectable
58+
{
59+
if (_selectable == selectable) {
60+
return;
61+
}
62+
63+
_selectable = selectable;
64+
65+
if (_selectable) {
66+
[self enableContextMenu];
67+
}
68+
else {
69+
[self disableContextMenu];
70+
}
71+
}
72+
5473
- (void)reactSetFrame:(CGRect)frame
5574
{
5675
// Text looks super weird if its frame is animated.
@@ -177,4 +196,72 @@ - (NSString *)accessibilityLabel
177196
return _textStorage.string;
178197
}
179198

199+
#pragma mark - Context Menu
200+
201+
- (void)enableContextMenu
202+
{
203+
_longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
204+
[self addGestureRecognizer:_longPressGestureRecognizer];
205+
}
206+
207+
- (void)disableContextMenu
208+
{
209+
[self removeGestureRecognizer:_longPressGestureRecognizer];
210+
_longPressGestureRecognizer = nil;
211+
}
212+
213+
- (void)handleLongPress:(UILongPressGestureRecognizer *)gesture
214+
{
215+
#if !TARGET_OS_TV
216+
UIMenuController *menuController = [UIMenuController sharedMenuController];
217+
218+
if (menuController.isMenuVisible) {
219+
return;
220+
}
221+
222+
if (!self.isFirstResponder) {
223+
[self becomeFirstResponder];
224+
}
225+
226+
[menuController setTargetRect:self.bounds inView:self];
227+
[menuController setMenuVisible:YES animated:YES];
228+
#endif
229+
}
230+
231+
- (BOOL)canBecomeFirstResponder
232+
{
233+
return _selectable;
234+
}
235+
236+
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
237+
{
238+
if (action == @selector(copy:)) {
239+
return YES;
240+
}
241+
242+
return [super canPerformAction:action withSender:sender];
243+
}
244+
245+
- (void)copy:(id)sender
246+
{
247+
#if !TARGET_OS_TV
248+
NSAttributedString *attributedString = _textStorage;
249+
250+
NSMutableDictionary *item = [NSMutableDictionary new];
251+
252+
NSData *rtf = [attributedString dataFromRange:NSMakeRange(0, attributedString.length)
253+
documentAttributes:@{NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType}
254+
error:nil];
255+
256+
if (rtf) {
257+
[item setObject:rtf forKey:(id)kUTTypeFlatRTFD];
258+
}
259+
260+
[item setObject:attributedString.string forKey:(id)kUTTypeUTF8PlainText];
261+
262+
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
263+
pasteboard.items = @[item];
264+
#endif
265+
}
266+
180267
@end

Libraries/Text/RCTTextManager.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ - (RCTShadowView *)shadowView
8484
RCT_EXPORT_SHADOW_PROPERTY(textShadowColor, UIColor)
8585
RCT_EXPORT_SHADOW_PROPERTY(adjustsFontSizeToFit, BOOL)
8686
RCT_EXPORT_SHADOW_PROPERTY(minimumFontScale, CGFloat)
87+
RCT_EXPORT_SHADOW_PROPERTY(selectable, BOOL)
8788

8889
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(NSDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
8990
{

Libraries/Text/Text.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,6 @@ const Text = React.createClass({
136136
onLongPress: React.PropTypes.func,
137137
/**
138138
* Lets the user select text, to use the native copy and paste functionality.
139-
*
140-
* @platform android
141139
*/
142140
selectable: React.PropTypes.bool,
143141
/**

0 commit comments

Comments
 (0)