Skip to content

Commit

Permalink
[BottomNavigation] Make KVO safe for nil. (#8083)
Browse files Browse the repository at this point in the history
For most of the `UITabBarItem` `NSString` properties, setting them to `nil` after assigning the item to the Bottom Navigation bar resulted in a raised exception that can crash an app. The KVO code is being updated to match the style in MDCButtonBar to make it safe for `nil` values.

Closes #8082
  • Loading branch information
Robert Moore committed Jul 25, 2019
1 parent 2245e91 commit 5af184f
Show file tree
Hide file tree
Showing 2 changed files with 389 additions and 103 deletions.
177 changes: 74 additions & 103 deletions components/BottomNavigation/src/MDCBottomNavigationBar.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
// The Bundle for string resources.
static NSString *const kBundleName = @"MaterialBottomNavigation.bundle";

// KVO context
static char *const kKVOContextMDCBottomNavigationBar = "kKVOContextMDCBottomNavigationBar";

static const CGFloat kMinItemWidth = 80;
static const CGFloat kPreferredItemWidth = 120;
static const CGFloat kMaxItemWidth = 168;
Expand All @@ -38,19 +41,6 @@
static const CGFloat kBarHeightStackedTitle = 56;
static const CGFloat kBarHeightAdjacentTitle = 40;
static const CGFloat kItemsHorizontalMargin = 12;
static NSString *const kBadgeColorString = @"badgeColor";
static NSString *const kBadgeValueString = @"badgeValue";
static NSString *const kAccessibilityValueString = @"accessibilityValue";
static NSString *const kImageString = @"image";
static NSString *const kSelectedImageString = @"selectedImage";
// TODO: - Change to NSKeyValueChangeNewKey
static NSString *const kNewString = @"new";
static NSString *const kTitleString = @"title";
static NSString *const kAccessibilityIdentifier = @"accessibilityIdentifier";
static NSString *const kAccessibilityLabel = @"accessibilityLabel";
static NSString *const kAccessibilityHint = @"accessibilityHint";
static NSString *const kIsAccessibilityElement = @"isAccessibilityElement";
static NSString *const kTitlePositionAdjustment = @"titlePositionAdjustment";

static NSString *const kOfAnnouncement = @"of";

Expand Down Expand Up @@ -342,72 +332,47 @@ - (void)dealloc {
[self removeObserversFromTabBarItems];
}

- (NSArray<NSString *> *)kvoKeyPaths {
static NSArray<NSString *> *keyPaths;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
keyPaths = @[
NSStringFromSelector(@selector(badgeColor)), NSStringFromSelector(@selector(badgeValue)),
NSStringFromSelector(@selector(title)), NSStringFromSelector(@selector(image)),
NSStringFromSelector(@selector(selectedImage)),
NSStringFromSelector(@selector(accessibilityValue)),
NSStringFromSelector(@selector(accessibilityLabel)),
NSStringFromSelector(@selector(accessibilityHint)),
NSStringFromSelector(@selector(accessibilityIdentifier)),
NSStringFromSelector(@selector(isAccessibilityElement)),
NSStringFromSelector(@selector(titlePositionAdjustment))
];
});
return keyPaths;
}

- (void)addObserversToTabBarItems {
NSArray<NSString *> *keyPaths = [self kvoKeyPaths];
for (UITabBarItem *item in self.items) {
[item addObserver:self
forKeyPath:kBadgeColorString
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kBadgeValueString
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kAccessibilityValueString
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kImageString
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kSelectedImageString
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kTitleString
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kAccessibilityIdentifier
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kAccessibilityLabel
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kAccessibilityHint
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kIsAccessibilityElement
options:NSKeyValueObservingOptionNew
context:nil];
[item addObserver:self
forKeyPath:kTitlePositionAdjustment
options:NSKeyValueObservingOptionNew
context:nil];
for (NSString *keyPath in keyPaths) {
[item addObserver:self
forKeyPath:keyPath
options:NSKeyValueObservingOptionNew
context:kKVOContextMDCBottomNavigationBar];
}
}
}

- (void)removeObserversFromTabBarItems {
NSArray<NSString *> *keyPaths = [self kvoKeyPaths];
for (UITabBarItem *item in self.items) {
@try {
[item removeObserver:self forKeyPath:kBadgeColorString];
[item removeObserver:self forKeyPath:kBadgeValueString];
[item removeObserver:self forKeyPath:kAccessibilityValueString];
[item removeObserver:self forKeyPath:kImageString];
[item removeObserver:self forKeyPath:kSelectedImageString];
[item removeObserver:self forKeyPath:kTitleString];
[item removeObserver:self forKeyPath:kAccessibilityIdentifier];
[item removeObserver:self forKeyPath:kAccessibilityLabel];
[item removeObserver:self forKeyPath:kAccessibilityHint];
[item removeObserver:self forKeyPath:kIsAccessibilityElement];
[item removeObserver:self forKeyPath:kTitlePositionAdjustment];
} @catch (NSException *exception) {
if (exception) {
// No need to do anything if there are no observers.
for (NSString *keyPath in keyPaths) {
@try {
[item removeObserver:self forKeyPath:keyPath context:kKVOContextMDCBottomNavigationBar];
} @catch (NSException *exception) {
if (exception) {
// No need to do anything if there are no observers.
}
}
}
}
Expand All @@ -417,39 +382,45 @@ - (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context {
if (!context) {
NSInteger selectedItemNum = 0;
for (NSUInteger i = 0; i < self.items.count; i++) {
UITabBarItem *item = self.items[i];
if (object == item) {
selectedItemNum = i;
break;
}
if (context == kKVOContextMDCBottomNavigationBar) {
if (!object) {
return;
}
NSUInteger itemIndex = [self.items indexOfObject:object];
if (itemIndex == NSNotFound || itemIndex >= _itemViews.count) {
return;
}
MDCBottomNavigationItemView *itemView = _itemViews[selectedItemNum];
if ([keyPath isEqualToString:kBadgeColorString]) {
itemView.badgeColor = change[kNewString];
} else if ([keyPath isEqualToString:kAccessibilityValueString]) {
itemView.accessibilityValue = change[NSKeyValueChangeNewKey];
} else if ([keyPath isEqualToString:kBadgeValueString]) {
itemView.badgeValue = change[kNewString];
} else if ([keyPath isEqualToString:kImageString]) {
itemView.image = change[kNewString];
} else if ([keyPath isEqualToString:kSelectedImageString]) {
itemView.selectedImage = change[kNewString];
} else if ([keyPath isEqualToString:kTitleString]) {
itemView.title = change[kNewString];
} else if ([keyPath isEqualToString:kAccessibilityIdentifier]) {
itemView.accessibilityIdentifier = change[kNewString];
} else if ([keyPath isEqualToString:kAccessibilityLabel]) {
itemView.accessibilityLabel = change[kNewString];
} else if ([keyPath isEqualToString:kAccessibilityHint]) {
itemView.accessibilityHint = change[kNewString];
} else if ([keyPath isEqualToString:kIsAccessibilityElement]) {
itemView.isAccessibilityElement = [change[kNewString] boolValue];
} else if ([keyPath isEqualToString:kTitlePositionAdjustment]) {
itemView.titlePositionAdjustment = [change[kNewString] UIOffsetValue];
id newValue = [object valueForKey:keyPath];
if (newValue == [NSNull null]) {
newValue = nil;
}

MDCBottomNavigationItemView *itemView = _itemViews[itemIndex];
if ([keyPath isEqualToString:NSStringFromSelector(@selector(badgeColor))]) {
itemView.badgeColor = newValue;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(accessibilityValue))]) {
itemView.accessibilityValue = newValue;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(badgeValue))]) {
itemView.badgeValue = newValue;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(image))]) {
itemView.image = newValue;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(selectedImage))]) {
itemView.selectedImage = newValue;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(title))]) {
itemView.title = newValue;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(accessibilityIdentifier))]) {
itemView.accessibilityIdentifier = newValue;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(accessibilityLabel))]) {
itemView.accessibilityLabel = newValue;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(accessibilityHint))]) {
itemView.accessibilityHint = newValue;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(isAccessibilityElement))]) {
itemView.isAccessibilityElement = [newValue boolValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(titlePositionAdjustment))]) {
itemView.titlePositionAdjustment = [newValue UIOffsetValue];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

Expand Down
Loading

0 comments on commit 5af184f

Please sign in to comment.