OLD | NEW |
(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/browser/ui/util/label_observer.h" |
| 6 |
| 7 #import <objc/runtime.h> |
| 8 |
| 9 #import "base/ios/weak_nsobject.h" |
| 10 #import "base/mac/scoped_block.h" |
| 11 #import "base/mac/scoped_nsobject.h" |
| 12 #include "base/strings/sys_string_conversions.h" |
| 13 |
| 14 namespace { |
| 15 // The key under which LabelObservers are associated with their labels. |
| 16 const void* const kLabelObserverKey = &kLabelObserverKey; |
| 17 // Attempts to convert |value| to a string. |
| 18 NSString* GetStringValue(id value) { |
| 19 if ([value isKindOfClass:[NSString class]]) |
| 20 return static_cast<NSString*>(value); |
| 21 if ([value respondsToSelector:@selector(string)]) |
| 22 return [value performSelector:@selector(string)]; |
| 23 return nil; |
| 24 } |
| 25 } |
| 26 |
| 27 @interface LabelObserver () { |
| 28 // The label being observed. |
| 29 base::WeakNSObject<UILabel> _label; |
| 30 // Arrays used to store registered actions. |
| 31 base::scoped_nsobject<NSMutableArray> _styleActions; |
| 32 base::scoped_nsobject<NSMutableArray> _layoutActions; |
| 33 base::scoped_nsobject<NSMutableArray> _textActions; |
| 34 } |
| 35 |
| 36 // Whether or not observer actions are currently being executed. This is used |
| 37 // to prevent infinite loops caused by a LinkObserverAction updating a |
| 38 // property on |_label|. |
| 39 @property(nonatomic, assign, getter=isRespondingToKVO) BOOL respondingToKVO; |
| 40 |
| 41 // Initializes a LabelObserver that observes |label|. |
| 42 - (instancetype)initWithLabel:(UILabel*)label NS_DESIGNATED_INITIALIZER; |
| 43 |
| 44 // Performs all LabelObserverActions in |actions|. |
| 45 - (void)performActions:(NSArray*)actions; |
| 46 |
| 47 // Takes |_label|'s values for each key from |styleKeys| and uses them to |
| 48 // construct a uniformly attributed value to use for |_label|'s attributedText. |
| 49 - (void)resetLabelAttributes; |
| 50 |
| 51 @end |
| 52 |
| 53 // Properties of UILabel that, when changed, will cause the label's attributed |
| 54 // text to change. |
| 55 static NSSet* styleKeys; |
| 56 // Properties of UILabel that invalidate the layout of the label if they change. |
| 57 static NSSet* layoutKeys; |
| 58 // Properties of UILabel that may invalidate the text of the label if they |
| 59 // change. |
| 60 static NSSet* textKeys; |
| 61 |
| 62 @implementation LabelObserver |
| 63 |
| 64 @synthesize respondingToKVO = _respondingToKVO; |
| 65 |
| 66 + (void)initialize { |
| 67 if (self == [LabelObserver class]) { |
| 68 styleKeys = [[NSSet alloc] initWithArray:@[ |
| 69 @"font", @"textColor", @"textAlignment", @"lineBreakMode", @"shadowColor", |
| 70 @"shadowOffset" |
| 71 ]]; |
| 72 layoutKeys = [[NSSet alloc] |
| 73 initWithArray:@[ @"bounds", @"frame", @"superview", @"center" ]]; |
| 74 textKeys = [[NSSet alloc] initWithArray:@[ @"text", @"attributedText" ]]; |
| 75 } |
| 76 } |
| 77 |
| 78 - (instancetype)initWithLabel:(UILabel*)label { |
| 79 if ((self = [super init])) { |
| 80 DCHECK(label); |
| 81 _label.reset(label); |
| 82 for (NSSet* keySet in @[ styleKeys, layoutKeys, textKeys ]) { |
| 83 for (NSString* key in keySet) { |
| 84 [_label addObserver:self |
| 85 forKeyPath:key |
| 86 options:NSKeyValueObservingOptionNew |
| 87 context:nullptr]; |
| 88 } |
| 89 } |
| 90 [self resetLabelAttributes]; |
| 91 } |
| 92 return self; |
| 93 } |
| 94 |
| 95 - (void)dealloc { |
| 96 for (NSSet* keySet in @[ styleKeys, layoutKeys, textKeys ]) { |
| 97 for (NSString* key in keySet) { |
| 98 [_label removeObserver:self forKeyPath:key]; |
| 99 } |
| 100 } |
| 101 [super dealloc]; |
| 102 } |
| 103 |
| 104 #pragma mark - Public interface |
| 105 |
| 106 + (instancetype)observerForLabel:(UILabel*)label { |
| 107 if (!label) |
| 108 return nil; |
| 109 id observer = objc_getAssociatedObject(label, kLabelObserverKey); |
| 110 if (!observer) { |
| 111 observer = [[LabelObserver alloc] initWithLabel:label]; |
| 112 objc_setAssociatedObject(label, kLabelObserverKey, observer, |
| 113 OBJC_ASSOCIATION_RETAIN_NONATOMIC); |
| 114 [observer release]; |
| 115 } |
| 116 return observer; |
| 117 } |
| 118 |
| 119 - (void)addStyleChangedAction:(LabelObserverAction)action { |
| 120 DCHECK(action); |
| 121 if (!_styleActions) |
| 122 _styleActions.reset([[NSMutableArray alloc] init]); |
| 123 base::mac::ScopedBlock<LabelObserverAction> actionCopy([action copy]); |
| 124 [_styleActions addObject:actionCopy]; |
| 125 } |
| 126 |
| 127 - (void)addLayoutChangedAction:(LabelObserverAction)action { |
| 128 DCHECK(action); |
| 129 if (!_layoutActions) |
| 130 _layoutActions.reset([[NSMutableArray alloc] init]); |
| 131 base::mac::ScopedBlock<LabelObserverAction> actionCopy([action copy]); |
| 132 [_layoutActions addObject:actionCopy]; |
| 133 } |
| 134 |
| 135 - (void)addTextChangedAction:(LabelObserverAction)action { |
| 136 DCHECK(action); |
| 137 if (!_textActions) |
| 138 _textActions.reset([[NSMutableArray alloc] init]); |
| 139 base::mac::ScopedBlock<LabelObserverAction> actionCopy([action copy]); |
| 140 [_textActions addObject:actionCopy]; |
| 141 } |
| 142 |
| 143 #pragma mark - |
| 144 |
| 145 - (void)performActions:(NSArray*)actions { |
| 146 for (LabelObserverAction action in actions) |
| 147 action(_label); |
| 148 } |
| 149 |
| 150 - (void)resetLabelAttributes { |
| 151 if ([_label attributedText] || ![_label text]) |
| 152 return; |
| 153 NSMutableDictionary* labelStyle = |
| 154 [NSMutableDictionary dictionaryWithCapacity:styleKeys.count]; |
| 155 for (NSString* property in styleKeys) |
| 156 labelStyle[property] = [_label valueForKey:property]; |
| 157 base::scoped_nsobject<NSAttributedString> attributedText( |
| 158 [[NSAttributedString alloc] initWithString:[_label text]]); |
| 159 [_label setAttributedText:attributedText]; |
| 160 for (NSString* property in styleKeys) |
| 161 [_label setValue:labelStyle[property] forKey:property]; |
| 162 } |
| 163 |
| 164 - (void)observeValueForKeyPath:(NSString*)key |
| 165 ofObject:(id)object |
| 166 change:(NSDictionary*)change |
| 167 context:(void*)context { |
| 168 if (self.respondingToKVO) |
| 169 return; |
| 170 self.respondingToKVO = YES; |
| 171 DCHECK_EQ(object, _label.get()); |
| 172 if ([styleKeys containsObject:key]) { |
| 173 [self performActions:_styleActions]; |
| 174 } else if ([layoutKeys containsObject:key]) { |
| 175 [self performActions:_layoutActions]; |
| 176 } else if ([textKeys containsObject:key]) { |
| 177 NSString* oldText = GetStringValue(change[NSKeyValueChangeOldKey]); |
| 178 NSString* newText = GetStringValue(change[NSKeyValueChangeNewKey]); |
| 179 if (![oldText isEqualToString:newText]) |
| 180 [self resetLabelAttributes]; |
| 181 [self performActions:_textActions]; |
| 182 } else { |
| 183 NOTREACHED() << "Unexpected label key <" << base::SysNSStringToUTF8(key) |
| 184 << "> observed"; |
| 185 } |
| 186 self.respondingToKVO = NO; |
| 187 } |
| 188 |
| 189 @end |
OLD | NEW |