| Index: ios/chrome/browser/ui/util/label_observer.mm
|
| diff --git a/ios/chrome/browser/ui/util/label_observer.mm b/ios/chrome/browser/ui/util/label_observer.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4fa5c8477581690d2d482fee10afa4b06c17458c
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/ui/util/label_observer.mm
|
| @@ -0,0 +1,189 @@
|
| +// 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/browser/ui/util/label_observer.h"
|
| +
|
| +#import <objc/runtime.h>
|
| +
|
| +#import "base/ios/weak_nsobject.h"
|
| +#import "base/mac/scoped_block.h"
|
| +#import "base/mac/scoped_nsobject.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +
|
| +namespace {
|
| +// The key under which LabelObservers are associated with their labels.
|
| +const void* const kLabelObserverKey = &kLabelObserverKey;
|
| +// Attempts to convert |value| to a string.
|
| +NSString* GetStringValue(id value) {
|
| + if ([value isKindOfClass:[NSString class]])
|
| + return static_cast<NSString*>(value);
|
| + if ([value respondsToSelector:@selector(string)])
|
| + return [value performSelector:@selector(string)];
|
| + return nil;
|
| +}
|
| +}
|
| +
|
| +@interface LabelObserver () {
|
| + // The label being observed.
|
| + base::WeakNSObject<UILabel> _label;
|
| + // Arrays used to store registered actions.
|
| + base::scoped_nsobject<NSMutableArray> _styleActions;
|
| + base::scoped_nsobject<NSMutableArray> _layoutActions;
|
| + base::scoped_nsobject<NSMutableArray> _textActions;
|
| +}
|
| +
|
| +// Whether or not observer actions are currently being executed. This is used
|
| +// to prevent infinite loops caused by a LinkObserverAction updating a
|
| +// property on |_label|.
|
| +@property(nonatomic, assign, getter=isRespondingToKVO) BOOL respondingToKVO;
|
| +
|
| +// Initializes a LabelObserver that observes |label|.
|
| +- (instancetype)initWithLabel:(UILabel*)label NS_DESIGNATED_INITIALIZER;
|
| +
|
| +// Performs all LabelObserverActions in |actions|.
|
| +- (void)performActions:(NSArray*)actions;
|
| +
|
| +// Takes |_label|'s values for each key from |styleKeys| and uses them to
|
| +// construct a uniformly attributed value to use for |_label|'s attributedText.
|
| +- (void)resetLabelAttributes;
|
| +
|
| +@end
|
| +
|
| +// Properties of UILabel that, when changed, will cause the label's attributed
|
| +// text to change.
|
| +static NSSet* styleKeys;
|
| +// Properties of UILabel that invalidate the layout of the label if they change.
|
| +static NSSet* layoutKeys;
|
| +// Properties of UILabel that may invalidate the text of the label if they
|
| +// change.
|
| +static NSSet* textKeys;
|
| +
|
| +@implementation LabelObserver
|
| +
|
| +@synthesize respondingToKVO = _respondingToKVO;
|
| +
|
| ++ (void)initialize {
|
| + if (self == [LabelObserver class]) {
|
| + styleKeys = [[NSSet alloc] initWithArray:@[
|
| + @"font", @"textColor", @"textAlignment", @"lineBreakMode", @"shadowColor",
|
| + @"shadowOffset"
|
| + ]];
|
| + layoutKeys = [[NSSet alloc]
|
| + initWithArray:@[ @"bounds", @"frame", @"superview", @"center" ]];
|
| + textKeys = [[NSSet alloc] initWithArray:@[ @"text", @"attributedText" ]];
|
| + }
|
| +}
|
| +
|
| +- (instancetype)initWithLabel:(UILabel*)label {
|
| + if ((self = [super init])) {
|
| + DCHECK(label);
|
| + _label.reset(label);
|
| + for (NSSet* keySet in @[ styleKeys, layoutKeys, textKeys ]) {
|
| + for (NSString* key in keySet) {
|
| + [_label addObserver:self
|
| + forKeyPath:key
|
| + options:NSKeyValueObservingOptionNew
|
| + context:nullptr];
|
| + }
|
| + }
|
| + [self resetLabelAttributes];
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (void)dealloc {
|
| + for (NSSet* keySet in @[ styleKeys, layoutKeys, textKeys ]) {
|
| + for (NSString* key in keySet) {
|
| + [_label removeObserver:self forKeyPath:key];
|
| + }
|
| + }
|
| + [super dealloc];
|
| +}
|
| +
|
| +#pragma mark - Public interface
|
| +
|
| ++ (instancetype)observerForLabel:(UILabel*)label {
|
| + if (!label)
|
| + return nil;
|
| + id observer = objc_getAssociatedObject(label, kLabelObserverKey);
|
| + if (!observer) {
|
| + observer = [[LabelObserver alloc] initWithLabel:label];
|
| + objc_setAssociatedObject(label, kLabelObserverKey, observer,
|
| + OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
| + [observer release];
|
| + }
|
| + return observer;
|
| +}
|
| +
|
| +- (void)addStyleChangedAction:(LabelObserverAction)action {
|
| + DCHECK(action);
|
| + if (!_styleActions)
|
| + _styleActions.reset([[NSMutableArray alloc] init]);
|
| + base::mac::ScopedBlock<LabelObserverAction> actionCopy([action copy]);
|
| + [_styleActions addObject:actionCopy];
|
| +}
|
| +
|
| +- (void)addLayoutChangedAction:(LabelObserverAction)action {
|
| + DCHECK(action);
|
| + if (!_layoutActions)
|
| + _layoutActions.reset([[NSMutableArray alloc] init]);
|
| + base::mac::ScopedBlock<LabelObserverAction> actionCopy([action copy]);
|
| + [_layoutActions addObject:actionCopy];
|
| +}
|
| +
|
| +- (void)addTextChangedAction:(LabelObserverAction)action {
|
| + DCHECK(action);
|
| + if (!_textActions)
|
| + _textActions.reset([[NSMutableArray alloc] init]);
|
| + base::mac::ScopedBlock<LabelObserverAction> actionCopy([action copy]);
|
| + [_textActions addObject:actionCopy];
|
| +}
|
| +
|
| +#pragma mark -
|
| +
|
| +- (void)performActions:(NSArray*)actions {
|
| + for (LabelObserverAction action in actions)
|
| + action(_label);
|
| +}
|
| +
|
| +- (void)resetLabelAttributes {
|
| + if ([_label attributedText] || ![_label text])
|
| + return;
|
| + NSMutableDictionary* labelStyle =
|
| + [NSMutableDictionary dictionaryWithCapacity:styleKeys.count];
|
| + for (NSString* property in styleKeys)
|
| + labelStyle[property] = [_label valueForKey:property];
|
| + base::scoped_nsobject<NSAttributedString> attributedText(
|
| + [[NSAttributedString alloc] initWithString:[_label text]]);
|
| + [_label setAttributedText:attributedText];
|
| + for (NSString* property in styleKeys)
|
| + [_label setValue:labelStyle[property] forKey:property];
|
| +}
|
| +
|
| +- (void)observeValueForKeyPath:(NSString*)key
|
| + ofObject:(id)object
|
| + change:(NSDictionary*)change
|
| + context:(void*)context {
|
| + if (self.respondingToKVO)
|
| + return;
|
| + self.respondingToKVO = YES;
|
| + DCHECK_EQ(object, _label.get());
|
| + if ([styleKeys containsObject:key]) {
|
| + [self performActions:_styleActions];
|
| + } else if ([layoutKeys containsObject:key]) {
|
| + [self performActions:_layoutActions];
|
| + } else if ([textKeys containsObject:key]) {
|
| + NSString* oldText = GetStringValue(change[NSKeyValueChangeOldKey]);
|
| + NSString* newText = GetStringValue(change[NSKeyValueChangeNewKey]);
|
| + if (![oldText isEqualToString:newText])
|
| + [self resetLabelAttributes];
|
| + [self performActions:_textActions];
|
| + } else {
|
| + NOTREACHED() << "Unexpected label key <" << base::SysNSStringToUTF8(key)
|
| + << "> observed";
|
| + }
|
| + self.respondingToKVO = NO;
|
| +}
|
| +
|
| +@end
|
|
|