Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(200)

Side by Side Diff: ios/chrome/today_extension/interactive_label.mm

Issue 2586713002: Upstream code and resources for Chrome on iOS extensions. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/today_extension/interactive_label.h"
6
7 #import "base/mac/scoped_block.h"
8 #import "base/mac/scoped_nsobject.h"
9 #import "ios/chrome/common/string_util.h"
10 #include "ios/chrome/today_extension/transparent_button.h"
11 #include "ios/chrome/today_extension/ui_util.h"
12
13 @interface InteractiveLabel ()<UITextViewDelegate>
14
15 - (CGRect)frameOfTextRange:(NSRange)range;
16
17 @end
18
19 // The UITextView must have selection enabled for the link detection to work.
20 // The FooterLabels must not have selection enabled. Disable it by disabling
21 // becomeFirstResponder.
22 @interface NoSelectionUITextView : UITextView
23 @end
24
25 @implementation NoSelectionUITextView
26
27 - (BOOL)canBecomeFirstResponder {
28 return NO;
29 }
30
31 @end
32
33 @implementation InteractiveLabel {
34 base::scoped_nsobject<NSString> _labelString;
35 base::scoped_nsobject<NSString> _buttonString;
36 base::scoped_nsobject<UITextView> _label;
37 base::mac::ScopedBlock<ProceduralBlock> _linkBlock;
38 base::mac::ScopedBlock<ProceduralBlock> _buttonBlock;
39 base::scoped_nsobject<TransparentButton> _activationButton;
40 NSRange _buttonRange;
41 base::scoped_nsobject<NSMutableAttributedString> _attributedText;
42 UIEdgeInsets _insets;
43 CGFloat _currentWidth;
44
45 // These constraints set the position of the button inside
46 base::scoped_nsobject<NSLayoutConstraint> _buttonTopConstraint;
47 base::scoped_nsobject<NSLayoutConstraint> _buttonLeftConstraint;
48 base::scoped_nsobject<NSLayoutConstraint> _buttonHeightConstraint;
49 base::scoped_nsobject<NSLayoutConstraint> _buttonWidthConstraint;
50 }
51
52 - (instancetype)initWithFrame:(CGRect)frame
53 labelString:(NSString*)labelString
54 fontSize:(CGFloat)fontSize
55 labelAlignment:(NSTextAlignment)labelAlignment
56 insets:(UIEdgeInsets)insets
57 buttonString:(NSString*)buttonString
58 linkBlock:(ProceduralBlock)linkBlock
59 buttonBlock:(ProceduralBlock)buttonBlock {
60 self = [super initWithFrame:frame];
61 if (self) {
62 _insets = insets;
63 _currentWidth = frame.size.width;
64 // When the first character of the UITextView text as a NSLinkAttributeName
65 // attribute, the lineSpacing attribute of the paragraph style is ignored.
66 // Add a zero width space so the first character is never in a link.
67 NSString* prefixedString =
68 [NSString stringWithFormat:@"\u200B%@", labelString];
69 _labelString.reset([prefixedString copy]);
70 _linkBlock.reset(linkBlock, base::scoped_policy::RETAIN);
71
72 _label.reset([[NoSelectionUITextView alloc] initWithFrame:CGRectZero]);
73 [_label setTranslatesAutoresizingMaskIntoConstraints:NO];
74 [_label setDelegate:self];
75 [_label setBackgroundColor:[UIColor clearColor]];
76 [_label setSelectable:YES];
77 [_label setEditable:NO];
78 // We want to get rid of the padding of the text in the UITextView.
79 [_label setTextContainerInset:UIEdgeInsetsZero];
80 [[_label textContainer] setLineFragmentPadding:0];
81 [self addSubview:_label];
82 [NSLayoutConstraint activateConstraints:@[
83 [[_label topAnchor] constraintEqualToAnchor:[self topAnchor]
84 constant:_insets.top],
85 [[_label bottomAnchor] constraintEqualToAnchor:[self bottomAnchor]
86 constant:_insets.bottom],
87 [[_label leadingAnchor] constraintEqualToAnchor:[self leadingAnchor]
88 constant:_insets.left],
89 [[_label trailingAnchor] constraintEqualToAnchor:[self trailingAnchor]
90 constant:-_insets.right]
91 ]];
92
93 NSRange linkRange;
94 NSString* text = ParseStringWithLink(_labelString, &linkRange);
95 [_label setAccessibilityTraits:[_label accessibilityTraits] |
96 UIAccessibilityTraitStaticText];
97 NSString* buttonText = nil;
98 _buttonRange = NSMakeRange(0, 0);
99 BOOL showButton = buttonString && buttonBlock;
100 if (showButton) {
101 _buttonString.reset([buttonString copy]);
102 _buttonBlock.reset(buttonBlock, base::scoped_policy::RETAIN);
103 _activationButton.reset(
104 [[TransparentButton alloc] initWithFrame:CGRectZero]);
105 [_activationButton addTarget:self
106 action:@selector(buttonPressed:)
107 forControlEvents:UIControlEventTouchUpInside];
108 [_activationButton setBackgroundColor:[UIColor clearColor]];
109 [_activationButton setInkColor:ui_util::InkColor()];
110 [_activationButton setCornerRadius:2];
111 [_activationButton setBorderWidth:1];
112 [_activationButton setBorderColor:ui_util::BorderColor()];
113 [self addSubview:_activationButton];
114 [self bringSubviewToFront:_activationButton];
115 _buttonTopConstraint.reset([[[_activationButton topAnchor]
116 constraintEqualToAnchor:[self topAnchor]] retain]);
117 _buttonLeftConstraint.reset([[[_activationButton leftAnchor]
118 constraintEqualToAnchor:[self leftAnchor]] retain]);
119 _buttonWidthConstraint.reset([[[_activationButton widthAnchor]
120 constraintEqualToConstant:0] retain]);
121 _buttonHeightConstraint.reset([[[_activationButton heightAnchor]
122 constraintEqualToConstant:0] retain]);
123 [NSLayoutConstraint activateConstraints:@[
124 _buttonTopConstraint, _buttonLeftConstraint, _buttonWidthConstraint,
125 _buttonHeightConstraint
126 ]];
127
128 // Add two spaces before and after the button label to add padding to the
129 // button.
130 buttonText = [NSString stringWithFormat:@" %@ ", _buttonString.get()];
131 // Replace spaces by non breaking spaces to prevent buttons from wrapping.
132 buttonText = [buttonText stringByReplacingOccurrencesOfString:@" "
133 withString:@"\u00A0"];
134 // Add space between the text and the button as separator.
135 text = [text stringByAppendingString:@" "];
136 _buttonRange = NSMakeRange([text length], [buttonText length]);
137 text = [text stringByAppendingString:buttonText];
138 }
139
140 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
141 [[NSMutableParagraphStyle alloc] init]);
142 UIFont* font = [UIFont fontWithName:@"Helvetica" size:fontSize];
143 [paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
144 [paragraphStyle setLineSpacing:2];
145
146 NSDictionary* normalAttributes = @{
147 NSParagraphStyleAttributeName : paragraphStyle,
148 NSFontAttributeName : font,
149 NSForegroundColorAttributeName : ui_util::FooterTextColor(),
150 NSBackgroundColorAttributeName : [UIColor clearColor],
151 };
152 NSDictionary* linkAttributes = @{
153 NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle),
154 NSForegroundColorAttributeName : ui_util::FooterTextColor(),
155 };
156 NSDictionary* hiddenAttributes = @{
157 NSForegroundColorAttributeName : [UIColor clearColor],
158 };
159
160 _attributedText.reset(
161 [[NSMutableAttributedString alloc] initWithString:text]);
162
163 [_attributedText setAttributes:normalAttributes
164 range:NSMakeRange(0, text.length)];
165
166 NSDictionary* linkURLAttributes = @{
167 NSFontAttributeName : font,
168 NSLinkAttributeName :
169 [NSURL URLWithString:@"chrometodayextension://footerButtonPressed"]
170 };
171 [_attributedText setAttributes:linkURLAttributes range:linkRange];
172
173 [_attributedText setAttributes:hiddenAttributes range:_buttonRange];
174
175 [_label setLinkTextAttributes:linkAttributes];
176
177 [_label setAttributedText:_attributedText];
178 [_label setTextAlignment:labelAlignment];
179
180 if (showButton) {
181 base::scoped_nsobject<NSMutableAttributedString> buttonAttributedTitle(
182 [[NSMutableAttributedString alloc] initWithString:buttonText]);
183 [paragraphStyle setLineSpacing:0];
184 NSDictionary* buttonAttributes = @{
185 NSParagraphStyleAttributeName : paragraphStyle,
186 NSFontAttributeName : font,
187 NSForegroundColorAttributeName : ui_util::TextColor(),
188 NSBackgroundColorAttributeName : [UIColor clearColor],
189 };
190 [buttonAttributedTitle setAttributes:buttonAttributes
191 range:NSMakeRange(0, [buttonText length])];
192 [_activationButton setAttributedTitle:buttonAttributedTitle
193 forState:UIControlStateNormal];
194 }
195 }
196 return self;
197 }
198
199 - (CGRect)frameOfTextRange:(NSRange)range {
200 // Temporary set editable flag to access the |UITextInput firstRectForRange:|
201 // method.
202 BOOL editableState = [_label isEditable];
203 [_label setEditable:YES];
204 UITextPosition* beginning = [_label beginningOfDocument];
205 UITextPosition* start =
206 [_label positionFromPosition:beginning offset:range.location];
207 UITextPosition* end = [_label positionFromPosition:start offset:range.length];
208 UITextRange* textRange = [_label textRangeFromPosition:start toPosition:end];
209 CGRect textFrame = [_label firstRectForRange:textRange];
210 textFrame = [_label convertRect:textFrame fromView:[_label textInputView]];
211 [_label setEditable:editableState];
212 return textFrame;
213 }
214
215 - (void)layoutSubviews {
216 [super layoutSubviews];
217
218 if (self.frame.size.width != _currentWidth) {
219 _currentWidth = self.frame.size.width;
220 [self invalidateIntrinsicContentSize];
221 }
222
223 if (_activationButton) {
224 CGRect textFrame = [self frameOfTextRange:_buttonRange];
225 CGRect buttonFrame = [self convertRect:textFrame fromView:_label];
226 [_buttonTopConstraint setConstant:buttonFrame.origin.y];
227 [_buttonLeftConstraint setConstant:buttonFrame.origin.x];
228 [_buttonWidthConstraint setConstant:buttonFrame.size.width];
229 [_buttonHeightConstraint setConstant:buttonFrame.size.height];
230 }
231 }
232
233 - (BOOL)textView:(UITextView*)textView
234 shouldInteractWithURL:(NSURL*)URL
235 inRange:(NSRange)characterRange {
236 _linkBlock.get()();
237 return NO;
238 }
239
240 - (void)buttonPressed:(id)sender {
241 _buttonBlock.get()();
242 }
243
244 - (CGSize)intrinsicContentSize {
245 return [self sizeThatFits:CGSizeMake(_currentWidth, CGFLOAT_MAX)];
246 }
247
248 - (CGSize)sizeThatFits:(CGSize)size {
249 if (![_attributedText length])
250 return CGSizeMake(size.width,
251 MIN(_insets.top + _insets.bottom, size.height));
252
253 // -sizeThatFits: doesn't behaves properly with UITextView.
254 // Therefore, we need to measure text size using
255 // -boundingRectWithSize:options:context:.
256 CGSize constraints =
257 CGSizeMake(size.width - _insets.left - _insets.right, CGFLOAT_MAX);
258 CGRect bounding = [_attributedText
259 boundingRectWithSize:constraints
260 options:static_cast<NSStringDrawingOptions>(
261 NSStringDrawingUsesLineFragmentOrigin |
262 NSStringDrawingUsesFontLeading)
263 context:nil];
264 return CGSizeMake(
265 size.width,
266 MIN(bounding.size.height + _insets.top + _insets.bottom, size.height));
267 }
268
269 @end
OLDNEW
« no previous file with comments | « ios/chrome/today_extension/interactive_label.h ('k') | ios/chrome/today_extension/lock_screen_state.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698