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

Unified 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 side-by-side diff with in-line comments
Download patch
« 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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ios/chrome/today_extension/interactive_label.mm
diff --git a/ios/chrome/today_extension/interactive_label.mm b/ios/chrome/today_extension/interactive_label.mm
new file mode 100644
index 0000000000000000000000000000000000000000..cba913a80dc377b5f4b794b22f2d4784db9f64ef
--- /dev/null
+++ b/ios/chrome/today_extension/interactive_label.mm
@@ -0,0 +1,269 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/today_extension/interactive_label.h"
+
+#import "base/mac/scoped_block.h"
+#import "base/mac/scoped_nsobject.h"
+#import "ios/chrome/common/string_util.h"
+#include "ios/chrome/today_extension/transparent_button.h"
+#include "ios/chrome/today_extension/ui_util.h"
+
+@interface InteractiveLabel ()<UITextViewDelegate>
+
+- (CGRect)frameOfTextRange:(NSRange)range;
+
+@end
+
+// The UITextView must have selection enabled for the link detection to work.
+// The FooterLabels must not have selection enabled. Disable it by disabling
+// becomeFirstResponder.
+@interface NoSelectionUITextView : UITextView
+@end
+
+@implementation NoSelectionUITextView
+
+- (BOOL)canBecomeFirstResponder {
+ return NO;
+}
+
+@end
+
+@implementation InteractiveLabel {
+ base::scoped_nsobject<NSString> _labelString;
+ base::scoped_nsobject<NSString> _buttonString;
+ base::scoped_nsobject<UITextView> _label;
+ base::mac::ScopedBlock<ProceduralBlock> _linkBlock;
+ base::mac::ScopedBlock<ProceduralBlock> _buttonBlock;
+ base::scoped_nsobject<TransparentButton> _activationButton;
+ NSRange _buttonRange;
+ base::scoped_nsobject<NSMutableAttributedString> _attributedText;
+ UIEdgeInsets _insets;
+ CGFloat _currentWidth;
+
+ // These constraints set the position of the button inside
+ base::scoped_nsobject<NSLayoutConstraint> _buttonTopConstraint;
+ base::scoped_nsobject<NSLayoutConstraint> _buttonLeftConstraint;
+ base::scoped_nsobject<NSLayoutConstraint> _buttonHeightConstraint;
+ base::scoped_nsobject<NSLayoutConstraint> _buttonWidthConstraint;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame
+ labelString:(NSString*)labelString
+ fontSize:(CGFloat)fontSize
+ labelAlignment:(NSTextAlignment)labelAlignment
+ insets:(UIEdgeInsets)insets
+ buttonString:(NSString*)buttonString
+ linkBlock:(ProceduralBlock)linkBlock
+ buttonBlock:(ProceduralBlock)buttonBlock {
+ self = [super initWithFrame:frame];
+ if (self) {
+ _insets = insets;
+ _currentWidth = frame.size.width;
+ // When the first character of the UITextView text as a NSLinkAttributeName
+ // attribute, the lineSpacing attribute of the paragraph style is ignored.
+ // Add a zero width space so the first character is never in a link.
+ NSString* prefixedString =
+ [NSString stringWithFormat:@"\u200B%@", labelString];
+ _labelString.reset([prefixedString copy]);
+ _linkBlock.reset(linkBlock, base::scoped_policy::RETAIN);
+
+ _label.reset([[NoSelectionUITextView alloc] initWithFrame:CGRectZero]);
+ [_label setTranslatesAutoresizingMaskIntoConstraints:NO];
+ [_label setDelegate:self];
+ [_label setBackgroundColor:[UIColor clearColor]];
+ [_label setSelectable:YES];
+ [_label setEditable:NO];
+ // We want to get rid of the padding of the text in the UITextView.
+ [_label setTextContainerInset:UIEdgeInsetsZero];
+ [[_label textContainer] setLineFragmentPadding:0];
+ [self addSubview:_label];
+ [NSLayoutConstraint activateConstraints:@[
+ [[_label topAnchor] constraintEqualToAnchor:[self topAnchor]
+ constant:_insets.top],
+ [[_label bottomAnchor] constraintEqualToAnchor:[self bottomAnchor]
+ constant:_insets.bottom],
+ [[_label leadingAnchor] constraintEqualToAnchor:[self leadingAnchor]
+ constant:_insets.left],
+ [[_label trailingAnchor] constraintEqualToAnchor:[self trailingAnchor]
+ constant:-_insets.right]
+ ]];
+
+ NSRange linkRange;
+ NSString* text = ParseStringWithLink(_labelString, &linkRange);
+ [_label setAccessibilityTraits:[_label accessibilityTraits] |
+ UIAccessibilityTraitStaticText];
+ NSString* buttonText = nil;
+ _buttonRange = NSMakeRange(0, 0);
+ BOOL showButton = buttonString && buttonBlock;
+ if (showButton) {
+ _buttonString.reset([buttonString copy]);
+ _buttonBlock.reset(buttonBlock, base::scoped_policy::RETAIN);
+ _activationButton.reset(
+ [[TransparentButton alloc] initWithFrame:CGRectZero]);
+ [_activationButton addTarget:self
+ action:@selector(buttonPressed:)
+ forControlEvents:UIControlEventTouchUpInside];
+ [_activationButton setBackgroundColor:[UIColor clearColor]];
+ [_activationButton setInkColor:ui_util::InkColor()];
+ [_activationButton setCornerRadius:2];
+ [_activationButton setBorderWidth:1];
+ [_activationButton setBorderColor:ui_util::BorderColor()];
+ [self addSubview:_activationButton];
+ [self bringSubviewToFront:_activationButton];
+ _buttonTopConstraint.reset([[[_activationButton topAnchor]
+ constraintEqualToAnchor:[self topAnchor]] retain]);
+ _buttonLeftConstraint.reset([[[_activationButton leftAnchor]
+ constraintEqualToAnchor:[self leftAnchor]] retain]);
+ _buttonWidthConstraint.reset([[[_activationButton widthAnchor]
+ constraintEqualToConstant:0] retain]);
+ _buttonHeightConstraint.reset([[[_activationButton heightAnchor]
+ constraintEqualToConstant:0] retain]);
+ [NSLayoutConstraint activateConstraints:@[
+ _buttonTopConstraint, _buttonLeftConstraint, _buttonWidthConstraint,
+ _buttonHeightConstraint
+ ]];
+
+ // Add two spaces before and after the button label to add padding to the
+ // button.
+ buttonText = [NSString stringWithFormat:@" %@ ", _buttonString.get()];
+ // Replace spaces by non breaking spaces to prevent buttons from wrapping.
+ buttonText = [buttonText stringByReplacingOccurrencesOfString:@" "
+ withString:@"\u00A0"];
+ // Add space between the text and the button as separator.
+ text = [text stringByAppendingString:@" "];
+ _buttonRange = NSMakeRange([text length], [buttonText length]);
+ text = [text stringByAppendingString:buttonText];
+ }
+
+ base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
+ [[NSMutableParagraphStyle alloc] init]);
+ UIFont* font = [UIFont fontWithName:@"Helvetica" size:fontSize];
+ [paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
+ [paragraphStyle setLineSpacing:2];
+
+ NSDictionary* normalAttributes = @{
+ NSParagraphStyleAttributeName : paragraphStyle,
+ NSFontAttributeName : font,
+ NSForegroundColorAttributeName : ui_util::FooterTextColor(),
+ NSBackgroundColorAttributeName : [UIColor clearColor],
+ };
+ NSDictionary* linkAttributes = @{
+ NSUnderlineStyleAttributeName : @(NSUnderlineStyleSingle),
+ NSForegroundColorAttributeName : ui_util::FooterTextColor(),
+ };
+ NSDictionary* hiddenAttributes = @{
+ NSForegroundColorAttributeName : [UIColor clearColor],
+ };
+
+ _attributedText.reset(
+ [[NSMutableAttributedString alloc] initWithString:text]);
+
+ [_attributedText setAttributes:normalAttributes
+ range:NSMakeRange(0, text.length)];
+
+ NSDictionary* linkURLAttributes = @{
+ NSFontAttributeName : font,
+ NSLinkAttributeName :
+ [NSURL URLWithString:@"chrometodayextension://footerButtonPressed"]
+ };
+ [_attributedText setAttributes:linkURLAttributes range:linkRange];
+
+ [_attributedText setAttributes:hiddenAttributes range:_buttonRange];
+
+ [_label setLinkTextAttributes:linkAttributes];
+
+ [_label setAttributedText:_attributedText];
+ [_label setTextAlignment:labelAlignment];
+
+ if (showButton) {
+ base::scoped_nsobject<NSMutableAttributedString> buttonAttributedTitle(
+ [[NSMutableAttributedString alloc] initWithString:buttonText]);
+ [paragraphStyle setLineSpacing:0];
+ NSDictionary* buttonAttributes = @{
+ NSParagraphStyleAttributeName : paragraphStyle,
+ NSFontAttributeName : font,
+ NSForegroundColorAttributeName : ui_util::TextColor(),
+ NSBackgroundColorAttributeName : [UIColor clearColor],
+ };
+ [buttonAttributedTitle setAttributes:buttonAttributes
+ range:NSMakeRange(0, [buttonText length])];
+ [_activationButton setAttributedTitle:buttonAttributedTitle
+ forState:UIControlStateNormal];
+ }
+ }
+ return self;
+}
+
+- (CGRect)frameOfTextRange:(NSRange)range {
+ // Temporary set editable flag to access the |UITextInput firstRectForRange:|
+ // method.
+ BOOL editableState = [_label isEditable];
+ [_label setEditable:YES];
+ UITextPosition* beginning = [_label beginningOfDocument];
+ UITextPosition* start =
+ [_label positionFromPosition:beginning offset:range.location];
+ UITextPosition* end = [_label positionFromPosition:start offset:range.length];
+ UITextRange* textRange = [_label textRangeFromPosition:start toPosition:end];
+ CGRect textFrame = [_label firstRectForRange:textRange];
+ textFrame = [_label convertRect:textFrame fromView:[_label textInputView]];
+ [_label setEditable:editableState];
+ return textFrame;
+}
+
+- (void)layoutSubviews {
+ [super layoutSubviews];
+
+ if (self.frame.size.width != _currentWidth) {
+ _currentWidth = self.frame.size.width;
+ [self invalidateIntrinsicContentSize];
+ }
+
+ if (_activationButton) {
+ CGRect textFrame = [self frameOfTextRange:_buttonRange];
+ CGRect buttonFrame = [self convertRect:textFrame fromView:_label];
+ [_buttonTopConstraint setConstant:buttonFrame.origin.y];
+ [_buttonLeftConstraint setConstant:buttonFrame.origin.x];
+ [_buttonWidthConstraint setConstant:buttonFrame.size.width];
+ [_buttonHeightConstraint setConstant:buttonFrame.size.height];
+ }
+}
+
+- (BOOL)textView:(UITextView*)textView
+ shouldInteractWithURL:(NSURL*)URL
+ inRange:(NSRange)characterRange {
+ _linkBlock.get()();
+ return NO;
+}
+
+- (void)buttonPressed:(id)sender {
+ _buttonBlock.get()();
+}
+
+- (CGSize)intrinsicContentSize {
+ return [self sizeThatFits:CGSizeMake(_currentWidth, CGFLOAT_MAX)];
+}
+
+- (CGSize)sizeThatFits:(CGSize)size {
+ if (![_attributedText length])
+ return CGSizeMake(size.width,
+ MIN(_insets.top + _insets.bottom, size.height));
+
+ // -sizeThatFits: doesn't behaves properly with UITextView.
+ // Therefore, we need to measure text size using
+ // -boundingRectWithSize:options:context:.
+ CGSize constraints =
+ CGSizeMake(size.width - _insets.left - _insets.right, CGFLOAT_MAX);
+ CGRect bounding = [_attributedText
+ boundingRectWithSize:constraints
+ options:static_cast<NSStringDrawingOptions>(
+ NSStringDrawingUsesLineFragmentOrigin |
+ NSStringDrawingUsesFontLeading)
+ context:nil];
+ return CGSizeMake(
+ size.width,
+ MIN(bounding.size.height + _insets.top + _insets.bottom, size.height));
+}
+
+@end
« 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