| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "ios/chrome/browser/ui/util/label_observer.h" | 5 #import "ios/chrome/browser/ui/util/label_observer.h" |
| 6 | 6 |
| 7 #import <objc/runtime.h> | 7 #import <objc/runtime.h> |
| 8 | 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" | 9 #include "base/strings/sys_string_conversions.h" |
| 13 | 10 |
| 11 #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 12 #error "This file requires ARC support." |
| 13 #endif |
| 14 |
| 14 namespace { | 15 namespace { |
| 15 // The key under which LabelObservers are associated with their labels. | 16 // The key under which LabelObservers are associated with their labels. |
| 16 const void* const kLabelObserverKey = &kLabelObserverKey; | 17 const void* const kLabelObserverKey = &kLabelObserverKey; |
| 17 // Attempts to convert |value| to a string. | 18 // Attempts to convert |value| to a string. |
| 18 NSString* GetStringValue(id value) { | 19 NSString* GetStringValue(id value) { |
| 19 if ([value isKindOfClass:[NSString class]]) | 20 if ([value isKindOfClass:[NSString class]]) |
| 20 return static_cast<NSString*>(value); | 21 return static_cast<NSString*>(value); |
| 21 if ([value respondsToSelector:@selector(string)]) | 22 if ([value respondsToSelector:@selector(string)]) |
| 22 return [value performSelector:@selector(string)]; | 23 return [value performSelector:@selector(string)]; |
| 23 return nil; | 24 return nil; |
| 24 } | 25 } |
| 25 } | 26 } |
| 26 | 27 |
| 27 @interface LabelObserver () { | 28 @interface LabelObserver () { |
| 28 // The label being observed. | 29 // The label being observed. |
| 29 base::WeakNSObject<UILabel> _label; | 30 __weak UILabel* _label; |
| 30 // Arrays used to store registered actions. | 31 // Arrays used to store registered actions. |
| 31 base::scoped_nsobject<NSMutableArray> _styleActions; | 32 NSMutableArray* _styleActions; |
| 32 base::scoped_nsobject<NSMutableArray> _layoutActions; | 33 NSMutableArray* _layoutActions; |
| 33 base::scoped_nsobject<NSMutableArray> _textActions; | 34 NSMutableArray* _textActions; |
| 34 } | 35 } |
| 35 | 36 |
| 36 // Whether or not observer actions are currently being executed. This is used | 37 // Whether or not observer actions are currently being executed. This is used |
| 37 // to prevent infinite loops caused by a LinkObserverAction updating a | 38 // to prevent infinite loops caused by a LinkObserverAction updating a |
| 38 // property on |_label|. | 39 // property on |_label|. |
| 39 @property(nonatomic, assign, getter=isRespondingToKVO) BOOL respondingToKVO; | 40 @property(nonatomic, assign, getter=isRespondingToKVO) BOOL respondingToKVO; |
| 40 | 41 |
| 41 // The number of times this observer has been asked to observe the label. When | 42 // The number of times this observer has been asked to observe the label. When |
| 42 // reaching zero, the label stops being observed. | 43 // reaching zero, the label stops being observed. |
| 43 @property(nonatomic, assign) NSInteger observingCount; | 44 @property(nonatomic, assign) NSInteger observingCount; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 82 ]]; | 83 ]]; |
| 83 layoutKeys = [[NSSet alloc] | 84 layoutKeys = [[NSSet alloc] |
| 84 initWithArray:@[ @"bounds", @"frame", @"superview", @"center" ]]; | 85 initWithArray:@[ @"bounds", @"frame", @"superview", @"center" ]]; |
| 85 textKeys = [[NSSet alloc] initWithArray:@[ @"text", @"attributedText" ]]; | 86 textKeys = [[NSSet alloc] initWithArray:@[ @"text", @"attributedText" ]]; |
| 86 } | 87 } |
| 87 } | 88 } |
| 88 | 89 |
| 89 - (instancetype)initWithLabel:(UILabel*)label { | 90 - (instancetype)initWithLabel:(UILabel*)label { |
| 90 if ((self = [super init])) { | 91 if ((self = [super init])) { |
| 91 DCHECK(label); | 92 DCHECK(label); |
| 92 _label.reset(label); | 93 _label = label; |
| 93 [self resetLabelAttributes]; | 94 [self resetLabelAttributes]; |
| 94 } | 95 } |
| 95 return self; | 96 return self; |
| 96 } | 97 } |
| 97 | 98 |
| 98 - (void)dealloc { | 99 - (void)dealloc { |
| 99 objc_setAssociatedObject(_label, kLabelObserverKey, nil, | 100 objc_setAssociatedObject(_label, kLabelObserverKey, nil, |
| 100 OBJC_ASSOCIATION_ASSIGN); | 101 OBJC_ASSOCIATION_ASSIGN); |
| 101 [super dealloc]; | |
| 102 } | 102 } |
| 103 | 103 |
| 104 #pragma mark - Public interface | 104 #pragma mark - Public interface |
| 105 | 105 |
| 106 + (instancetype)observerForLabel:(UILabel*)label { | 106 + (instancetype)observerForLabel:(UILabel*)label { |
| 107 if (!label) | 107 if (!label) |
| 108 return nil; | 108 return nil; |
| 109 id observer = objc_getAssociatedObject(label, kLabelObserverKey); | 109 id observer = objc_getAssociatedObject(label, kLabelObserverKey); |
| 110 if (!observer) { | 110 if (!observer) { |
| 111 observer = [[LabelObserver alloc] initWithLabel:label]; | 111 observer = [[LabelObserver alloc] initWithLabel:label]; |
| 112 objc_setAssociatedObject(label, kLabelObserverKey, observer, | 112 objc_setAssociatedObject(label, kLabelObserverKey, observer, |
| 113 OBJC_ASSOCIATION_ASSIGN); | 113 OBJC_ASSOCIATION_ASSIGN); |
| 114 [observer autorelease]; | |
| 115 } | 114 } |
| 116 return observer; | 115 return observer; |
| 117 } | 116 } |
| 118 | 117 |
| 119 - (void)startObserving { | 118 - (void)startObserving { |
| 120 if (self.observingCount == 0) { | 119 if (self.observingCount == 0) { |
| 121 [self registerAsObserver]; | 120 [self registerAsObserver]; |
| 122 } | 121 } |
| 123 self.observingCount++; | 122 self.observingCount++; |
| 124 } | 123 } |
| 125 | 124 |
| 126 - (void)stopObserving { | 125 - (void)stopObserving { |
| 127 if (self.observingCount == 0) | 126 if (self.observingCount == 0) |
| 128 return; | 127 return; |
| 129 | 128 |
| 130 self.observingCount--; | 129 self.observingCount--; |
| 131 if (self.observingCount == 0) { | 130 if (self.observingCount == 0) { |
| 132 [self removeObserver]; | 131 [self removeObserver]; |
| 133 } | 132 } |
| 134 } | 133 } |
| 135 | 134 |
| 136 - (void)addStyleChangedAction:(LabelObserverAction)action { | 135 - (void)addStyleChangedAction:(LabelObserverAction)action { |
| 137 DCHECK(action); | 136 DCHECK(action); |
| 138 if (!_styleActions) | 137 if (!_styleActions) |
| 139 _styleActions.reset([[NSMutableArray alloc] init]); | 138 _styleActions = [[NSMutableArray alloc] init]; |
| 140 base::mac::ScopedBlock<LabelObserverAction> actionCopy([action copy]); | 139 LabelObserverAction actionCopy = [action copy]; |
| 141 [_styleActions addObject:actionCopy]; | 140 [_styleActions addObject:actionCopy]; |
| 142 } | 141 } |
| 143 | 142 |
| 144 - (void)addLayoutChangedAction:(LabelObserverAction)action { | 143 - (void)addLayoutChangedAction:(LabelObserverAction)action { |
| 145 DCHECK(action); | 144 DCHECK(action); |
| 146 if (!_layoutActions) | 145 if (!_layoutActions) |
| 147 _layoutActions.reset([[NSMutableArray alloc] init]); | 146 _layoutActions = [[NSMutableArray alloc] init]; |
| 148 base::mac::ScopedBlock<LabelObserverAction> actionCopy([action copy]); | 147 LabelObserverAction actionCopy = [action copy]; |
| 149 [_layoutActions addObject:actionCopy]; | 148 [_layoutActions addObject:actionCopy]; |
| 150 } | 149 } |
| 151 | 150 |
| 152 - (void)addTextChangedAction:(LabelObserverAction)action { | 151 - (void)addTextChangedAction:(LabelObserverAction)action { |
| 153 DCHECK(action); | 152 DCHECK(action); |
| 154 if (!_textActions) | 153 if (!_textActions) |
| 155 _textActions.reset([[NSMutableArray alloc] init]); | 154 _textActions = [[NSMutableArray alloc] init]; |
| 156 base::mac::ScopedBlock<LabelObserverAction> actionCopy([action copy]); | 155 LabelObserverAction actionCopy = [action copy]; |
| 157 [_textActions addObject:actionCopy]; | 156 [_textActions addObject:actionCopy]; |
| 158 } | 157 } |
| 159 | 158 |
| 160 #pragma mark - | 159 #pragma mark - |
| 161 | 160 |
| 162 - (void)registerAsObserver { | 161 - (void)registerAsObserver { |
| 163 for (NSSet* keySet in @[ styleKeys, layoutKeys, textKeys ]) { | 162 for (NSSet* keySet in @[ styleKeys, layoutKeys, textKeys ]) { |
| 164 for (NSString* key in keySet) { | 163 for (NSString* key in keySet) { |
| 165 [_label addObserver:self | 164 [_label addObserver:self |
| 166 forKeyPath:key | 165 forKeyPath:key |
| (...skipping 16 matching lines...) Expand all Loading... |
| 183 action(_label); | 182 action(_label); |
| 184 } | 183 } |
| 185 | 184 |
| 186 - (void)resetLabelAttributes { | 185 - (void)resetLabelAttributes { |
| 187 if ([_label attributedText] || ![_label text]) | 186 if ([_label attributedText] || ![_label text]) |
| 188 return; | 187 return; |
| 189 NSMutableDictionary* labelStyle = | 188 NSMutableDictionary* labelStyle = |
| 190 [NSMutableDictionary dictionaryWithCapacity:styleKeys.count]; | 189 [NSMutableDictionary dictionaryWithCapacity:styleKeys.count]; |
| 191 for (NSString* property in styleKeys) | 190 for (NSString* property in styleKeys) |
| 192 labelStyle[property] = [_label valueForKey:property]; | 191 labelStyle[property] = [_label valueForKey:property]; |
| 193 base::scoped_nsobject<NSAttributedString> attributedText( | 192 NSAttributedString* attributedText = |
| 194 [[NSAttributedString alloc] initWithString:[_label text]]); | 193 [[NSAttributedString alloc] initWithString:[_label text]]; |
| 195 [_label setAttributedText:attributedText]; | 194 [_label setAttributedText:attributedText]; |
| 196 for (NSString* property in styleKeys) | 195 for (NSString* property in styleKeys) |
| 197 [_label setValue:labelStyle[property] forKey:property]; | 196 [_label setValue:labelStyle[property] forKey:property]; |
| 198 } | 197 } |
| 199 | 198 |
| 200 - (void)observeValueForKeyPath:(NSString*)key | 199 - (void)observeValueForKeyPath:(NSString*)key |
| 201 ofObject:(id)object | 200 ofObject:(id)object |
| 202 change:(NSDictionary*)change | 201 change:(NSDictionary*)change |
| 203 context:(void*)context { | 202 context:(void*)context { |
| 204 if (self.respondingToKVO) | 203 if (self.respondingToKVO) |
| 205 return; | 204 return; |
| 206 self.respondingToKVO = YES; | 205 self.respondingToKVO = YES; |
| 207 DCHECK_EQ(object, _label.get()); | 206 DCHECK_EQ(object, _label); |
| 208 if ([styleKeys containsObject:key]) { | 207 if ([styleKeys containsObject:key]) { |
| 209 [self performActions:_styleActions]; | 208 [self performActions:_styleActions]; |
| 210 } else if ([layoutKeys containsObject:key]) { | 209 } else if ([layoutKeys containsObject:key]) { |
| 211 [self performActions:_layoutActions]; | 210 [self performActions:_layoutActions]; |
| 212 } else if ([textKeys containsObject:key]) { | 211 } else if ([textKeys containsObject:key]) { |
| 213 NSString* oldText = GetStringValue(change[NSKeyValueChangeOldKey]); | 212 NSString* oldText = GetStringValue(change[NSKeyValueChangeOldKey]); |
| 214 NSString* newText = GetStringValue(change[NSKeyValueChangeNewKey]); | 213 NSString* newText = GetStringValue(change[NSKeyValueChangeNewKey]); |
| 215 if (![oldText isEqualToString:newText]) | 214 if (![oldText isEqualToString:newText]) |
| 216 [self resetLabelAttributes]; | 215 [self resetLabelAttributes]; |
| 217 [self performActions:_textActions]; | 216 [self performActions:_textActions]; |
| 218 } else { | 217 } else { |
| 219 NOTREACHED() << "Unexpected label key <" << base::SysNSStringToUTF8(key) | 218 NOTREACHED() << "Unexpected label key <" << base::SysNSStringToUTF8(key) |
| 220 << "> observed"; | 219 << "> observed"; |
| 221 } | 220 } |
| 222 self.respondingToKVO = NO; | 221 self.respondingToKVO = NO; |
| 223 } | 222 } |
| 224 | 223 |
| 225 @end | 224 @end |
| OLD | NEW |