blob: da33c5e008f555504f81b86e5717e9891019d6a9 [file] [log] [blame] [edit]
// Copyright 2018-present the Material Components for iOS authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "MDCBottomNavigationBarController.h"
#import <CoreGraphics/CoreGraphics.h>
#import "private/MDCBottomNavigationBar+Private.h"
#import "private/MDCBottomNavigationLargeItemDialogView.h"
#import "MaterialBottomNavigation.h"
// A context for Key Value Observing
static void *const kObservationContext = (void *)&kObservationContext;
static const CGFloat kLargeItemViewHeight = 210;
static const CGFloat kLargeItemViewWidth = 210;
static const NSTimeInterval kLargeItemViewAnimationDuration = 0.1;
static const NSTimeInterval kLongPressMinimumPressDuration = 0.2;
static const NSTimeInterval kNavigationBarHideShowAnimationDuration = 0.3;
static const NSUInteger kLongPressNumberOfTouchesRequired = 1;
static NSString *const kSelectedViewControllerRestorationKey = @"selectedViewController";
/**
The transform of the large item view when it is in a transitional state (appearing or
dismissing).
*/
static CGAffineTransform LargeItemViewAnimationTransitionTransform() {
return CGAffineTransformScale(CGAffineTransformIdentity, (CGFloat)0.97, (CGFloat)0.97);
}
/**
Decodes a view controller with the given key from the given coder. If the coder does not have an
object associated with the key or the value is not a @c UIViewController this function returns nil.
*/
static UIViewController *_Nullable DecodeViewController(NSCoder *coder, NSString *key) {
if (![coder containsValueForKey:key]) {
return nil;
}
UIViewController *viewController = [coder decodeObjectForKey:key];
if ([viewController isKindOfClass:[UIViewController class]]) {
return viewController;
}
return nil;
}
@interface MDCBottomNavigationBarController ()
/** The view that hosts the content for the selected view controller. */
@property(nonatomic, strong) UIView *content;
/** The gesture recognizer for detecting long presses on tab bar items. */
@property(nonatomic, strong, nonnull)
UILongPressGestureRecognizer *navigationBarLongPressRecognizer;
/** The dialog view to display a large item view. */
@property(nonatomic, strong, nullable) MDCBottomNavigationLargeItemDialogView *largeItemDialog;
/** Returns if the long press gesture recognizer has been added to the navigation bar. */
@property(nonatomic, readonly, getter=isNavigationBarLongPressRecognizerRegistered)
BOOL navigationBarLongPressRecognizerRegistered;
/**
Indicates if the large item view is in the process of dismissing. This is to ensure that the dialog
animation is not started again if it is already animating a dismissal.
*/
@property(nonatomic, getter=isDismissingLargeItemDialog) BOOL dismissingLargeItemView;
/** The constraint between the bottom of @c navigationBar and its superview. */
@property(nonatomic, strong, nullable) NSLayoutConstraint *navigationBarBottomAnchorConstraint;
/** The constraint between @c navigationBar.barItemsBottomAnchor and the bottom of the safe area. */
@property(nonatomic, strong, nullable) NSLayoutConstraint *navigationBarItemsBottomAnchorConstraint;
@end
@implementation MDCBottomNavigationBarController
- (instancetype)init {
self = [super init];
if (self) {
_navigationBar = [[MDCBottomNavigationBar alloc] init];
_content = [[UIView alloc] init];
_selectedIndex = NSNotFound;
_dismissingLargeItemView = NO;
if (@available(iOS 13.0, *)) {
_longPressPopUpViewEnabled = NO;
} else {
_longPressPopUpViewEnabled = YES;
}
[_navigationBar addObserver:self
forKeyPath:NSStringFromSelector(@selector(items))
options:NSKeyValueObservingOptionNew
context:kObservationContext];
}
return self;
}
- (void)dealloc {
[_navigationBar removeObserver:self forKeyPath:NSStringFromSelector(@selector(items))];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationBar.delegate = self;
// Add subviews and create their constraints
[self.view addSubview:self.content];
[self.view addSubview:self.navigationBar];
[self loadConstraints];
if (self.isLongPressPopUpViewEnabled && !self.isNavigationBarLongPressRecognizerRegistered) {
[self.navigationBar addGestureRecognizer:self.navigationBarLongPressRecognizer];
}
}
- (void)viewSafeAreaInsetsDidChange {
if (@available(iOS 11.0, *)) {
[super viewSafeAreaInsetsDidChange];
}
[self updateNavigationBarInsets];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self updateNavigationBarInsets];
}
- (void)setSelectedViewController:(nullable UIViewController *)selectedViewController {
// Assert that the given VC is one of our view controllers or it is nil (we are unselecting)
NSAssert(
selectedViewController == nil || [self.viewControllers containsObject:selectedViewController],
@"Attempting to set BottomBarViewControllers to a view controller it does not contain");
// Early return if we are already set to the given VC
if (self.selectedViewController == selectedViewController) {
return;
}
// Remove current VC and add new one.
[self.selectedViewController.view removeFromSuperview];
[self.content addSubview:selectedViewController.view];
[self addConstraintsForChildViewControllerView:selectedViewController.view];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
// Set the iVar and update selected index
_selectedViewController = selectedViewController;
self.selectedIndex = [self.viewControllers indexOfObject:selectedViewController];
}
- (void)setSelectedIndex:(NSUInteger)selectedIndex {
// If we are setting to NSNotFound deselect the items
if (selectedIndex == NSNotFound) {
[self deselectCurrentItem];
return;
}
BOOL outOfBounds = selectedIndex >= self.viewControllers.count ||
selectedIndex >= self.navigationBar.items.count;
NSAssert(!outOfBounds,
@"Attempting to set BottomBarViewController's selectedIndex to %li. This"
" value is not within the bounds of the navigation bar's items and/or view controllers",
(unsigned long)selectedIndex);
// Early return if we are out of bounds or if the the index is already selected.
if (outOfBounds || selectedIndex == _selectedIndex) {
return;
}
// Update the selected index value and views.
_selectedIndex = selectedIndex;
[self updateViewsForSelectedIndex:selectedIndex];
}
- (void)addNewChildViewControllers:(NSArray<UIViewController *> *)newChildViewControllers {
for (UIViewController *viewController in newChildViewControllers) {
[self addChildViewController:viewController];
[viewController didMoveToParentViewController:self];
}
}
- (NSArray<UIViewController *> *)viewControllers {
return [self.childViewControllers copy];
}
- (void)setViewControllers:(NSArray<UIViewController *> *)viewControllers {
[self deselectCurrentItem];
[self removeExistingViewControllers];
[self addNewChildViewControllers:[viewControllers copy]];
self.navigationBar.items = [self tabBarItemsForViewControllers:self.childViewControllers];
self.selectedViewController = self.childViewControllers.firstObject;
}
- (void)setLongPressPopUpViewEnabled:(BOOL)isLongPressPopUpViewEnabled {
_longPressPopUpViewEnabled = isLongPressPopUpViewEnabled;
if (isLongPressPopUpViewEnabled && !self.isNavigationBarLongPressRecognizerRegistered) {
[self.navigationBar addGestureRecognizer:self.navigationBarLongPressRecognizer];
} else if (!isLongPressPopUpViewEnabled && self.isNavigationBarLongPressRecognizerRegistered) {
[self.navigationBar removeGestureRecognizer:self.navigationBarLongPressRecognizer];
}
}
- (UIViewController *)childViewControllerForStatusBarStyle {
return self.selectedViewController;
}
- (UIViewController *)childViewControllerForStatusBarHidden {
return self.selectedViewController;
}
- (UIViewController *)childViewControllerForHomeIndicatorAutoHidden {
return self.selectedViewController;
}
- (UIViewController *)childViewControllerForScreenEdgesDeferringSystemGestures {
return self.selectedViewController;
}
- (UILongPressGestureRecognizer *)navigationBarLongPressRecognizer {
if (!_navigationBarLongPressRecognizer) {
_navigationBarLongPressRecognizer = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:@selector(handleNavigationBarLongPress:)];
_navigationBarLongPressRecognizer.numberOfTouchesRequired = kLongPressNumberOfTouchesRequired;
_navigationBarLongPressRecognizer.minimumPressDuration = kLongPressMinimumPressDuration;
}
return _navigationBarLongPressRecognizer;
}
#pragma mark - NavigationBar visibility
- (void)setNavigationBarHidden:(BOOL)navigationBarHidden {
[self setNavigationBarHidden:navigationBarHidden animated:NO];
}
- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
if (hidden == _navigationBarHidden) {
return;
}
_navigationBarHidden = hidden;
MDCBottomNavigationBar *navigationBar = self.navigationBar;
self.navigationBarItemsBottomAnchorConstraint.active = !hidden;
self.navigationBarBottomAnchorConstraint.constant =
hidden ? CGRectGetHeight(navigationBar.frame) : 0;
void (^completionBlock)(BOOL) = ^(BOOL finished) {
// Update the end hidden state of the navigation bar if it was not interrupted (the end state
// matches the current state). Otherwise an already scheduled animation will take care of this.
if (finished && !hidden != !self.navigationBarItemsBottomAnchorConstraint.active) {
navigationBar.hidden = hidden;
}
};
// Immediatelly update the navigation bar's hidden state when it is going to become visible to be
// able to see the animation).
if (!hidden) {
navigationBar.hidden = hidden;
}
NSTimeInterval duration = animated ? kNavigationBarHideShowAnimationDuration : 0;
[UIView animateWithDuration:duration
animations:^{
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
[self updateNavigationBarInsets];
}
completion:completionBlock];
}
#pragma mark - MDCBottomNavigationBarDelegate
- (void)bottomNavigationBar:(MDCBottomNavigationBar *)bottomNavigationBar
didSelectItem:(UITabBarItem *)item {
// Early return if we cannot find the view controller.
NSUInteger index = [self.navigationBar.items indexOfObject:item];
if (index >= [self.viewControllers count] || index == NSNotFound) {
return;
}
// Update selected view controller
UIViewController *selectedViewController = [self.viewControllers objectAtIndex:index];
self.selectedViewController = selectedViewController;
// Notify the delegate.
if ([self.delegate respondsToSelector:@selector(bottomNavigationBarController:
didSelectViewController:)]) {
[self.delegate bottomNavigationBarController:self
didSelectViewController:selectedViewController];
}
}
- (BOOL)bottomNavigationBar:(MDCBottomNavigationBar *)bottomNavigationBar
shouldSelectItem:(UITabBarItem *)item {
NSUInteger index = [self.navigationBar.items indexOfObject:item];
if (index >= [self.viewControllers count] || index == NSNotFound) {
return NO;
}
// Pass the response to the delegate if they want to handle this request.
if ([self.delegate respondsToSelector:@selector(bottomNavigationBarController:
shouldSelectViewController:)]) {
UIViewController *viewControllerToSelect = [self.viewControllers objectAtIndex:index];
return [self.delegate bottomNavigationBarController:self
shouldSelectViewController:viewControllerToSelect];
}
return YES;
}
#pragma mark - Key Value Observation Methods
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context {
if (context != kObservationContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}
id newValue = [change objectForKey:NSKeyValueChangeNewKey];
if (object == self.navigationBar &&
[keyPath isEqualToString:NSStringFromSelector(@selector(items))] &&
[newValue isKindOfClass:[NSArray class]]) {
[self didUpdateNavigationBarItemsWithNewValue:(NSArray *)newValue];
}
}
- (void)didUpdateNavigationBarItemsWithNewValue:(NSArray *)items {
// Verify tab bar items correspond with the view controllers tab bar items.
if (items.count != self.viewControllers.count) {
[[self unauthorizedItemsChangedException] raise];
}
// Verify each new and the view controller's tab bar items are equal.
for (NSUInteger i = 0; i < self.viewControllers.count; i++) {
UITabBarItem *viewControllerTabBarItem = [self.viewControllers objectAtIndex:i].tabBarItem;
UITabBarItem *newTabBarItem = [items objectAtIndex:i];
if (![viewControllerTabBarItem isEqual:newTabBarItem]) {
[[self unauthorizedItemsChangedException] raise];
}
}
}
#pragma mark - Touch Events
/** Handles long press gesture recognizer event updates. */
- (void)handleNavigationBarLongPress:(UIGestureRecognizer *)recognizer {
CGPoint touchPoint = [recognizer locationInView:self.navigationBar];
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStateChanged:
[self handleNavigationBarLongPressUpdatedForPoint:touchPoint];
break;
default:
[self handleNavigationBarLongPressEndedForPoint:touchPoint];
break;
}
}
/**
Handles when the navigation bar long press gesture recognizer gesture has been initiated or the
touch point was updated.
@param point CGPoint The point within @c navigationBar coordinate space.
*/
- (void)handleNavigationBarLongPressUpdatedForPoint:(CGPoint)point {
if (!self.isContentSizeCategoryAccessibilityCategory) {
return;
}
UITabBarItem *item = [self.navigationBar tabBarItemForPoint:point];
if (!item && CGRectContainsPoint(self.navigationBar.bounds, point)) {
// The item may be nil when the touch is still within the frame of the navigation bar, but not
// within the frame of an item view. In this case the large item view should still display the
// last long pressed item.
return;
} else if (!item) {
[self handleNavigationBarLongPressEndedForPoint:point];
return;
}
if (!self.largeItemDialog) {
self.largeItemDialog = [[MDCBottomNavigationLargeItemDialogView alloc] init];
}
[self.largeItemDialog updateWithTabBarItem:item];
[self showLargeItemDialog];
}
/**
Handles when the navigation bar long press gesture recognizer gesture has concluded.
@param point CGPoint The point within @c navigationBar coordinate space.
*/
- (void)handleNavigationBarLongPressEndedForPoint:(CGPoint)point {
UITabBarItem *item = [self.navigationBar tabBarItemForPoint:point];
NSUInteger index = [self.navigationBar.items indexOfObject:item];
if (index != NSNotFound && index < self.viewControllers.count) {
self.selectedIndex = index;
}
[self dismissLargeItemDialog];
}
#pragma mark - State Restoration Methods
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
for (UIViewController *childViewController in self.childViewControllers) {
if (childViewController.restorationIdentifier.length > 0) {
[coder encodeObject:childViewController];
}
}
if (self.selectedViewController) {
[coder encodeObject:self.selectedViewController forKey:kSelectedViewControllerRestorationKey];
}
[super encodeRestorableStateWithCoder:coder];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
UIViewController *selectedViewController =
DecodeViewController(coder, kSelectedViewControllerRestorationKey);
if (selectedViewController && [self.viewControllers containsObject:selectedViewController]) {
self.selectedViewController = selectedViewController;
}
[super decodeRestorableStateWithCoder:coder];
}
#pragma mark - Private Methods
- (void)removeExistingViewControllers {
NSArray<UIViewController *> *childViewControllers = self.childViewControllers;
for (UIViewController *childViewController in childViewControllers) {
[childViewController willMoveToParentViewController:nil];
[childViewController.view removeFromSuperview];
[childViewController removeFromParentViewController];
}
}
/**
Adjusts all relevant insets in subviews and the selected child view controller. This include @c
safeAreaInsets and scroll view insets. This will ensure that although the child view controller's
view is positioned behind the bar, it can still lay out its content above the Bottom Navigation
bar. For a UIScrollView, this means manipulating both @c contentInset and
@c scrollIndicatorInsets.
*/
- (void)updateNavigationBarInsets {
UIEdgeInsets currentSafeAreaInsets = UIEdgeInsetsZero;
CGFloat navigationBarHeight =
self.isNavigationBarHidden ? 0 : CGRectGetHeight(self.navigationBar.frame);
if (@available(iOS 11.0, *)) {
currentSafeAreaInsets = self.view.safeAreaInsets;
}
UIEdgeInsets additionalSafeAreaInsets =
UIEdgeInsetsMake(0, 0, navigationBarHeight - currentSafeAreaInsets.bottom, 0);
if (@available(iOS 11.0, *)) {
self.selectedViewController.additionalSafeAreaInsets = additionalSafeAreaInsets;
} else {
self.content.layoutMargins = additionalSafeAreaInsets;
// Based on an article by Ryan Fox, attempt to adjust the contentInset of either the view
// or its first subview.
// https://medium.com/@wailord/the-automaticallyadjustsscrollviewinsets-rabbit-hole-b9153a769ce9
if (self.selectedViewController.automaticallyAdjustsScrollViewInsets) {
UIScrollView *scrollView;
if ([self.selectedViewController.view isKindOfClass:[UIScrollView class]]) {
scrollView = (UIScrollView *)self.selectedViewController.view;
} else if ([self.selectedViewController.view.subviews.firstObject
isKindOfClass:[UIScrollView class]]) {
scrollView = (UIScrollView *)self.selectedViewController.view.subviews.firstObject;
}
// Ideally, we would probably set an associated object that tracks the insets applied
// as a result of the Bottom Navigation bar, then we could compute the difference between that
// value and this value.
// However, because Bottom Navigation is intended to be used at the application root (it is
// meant for app-wide navigation) we assume no other view controller is applying a content
// to the child. If such a situation exists, most clients should probably manage their own
// contentInset values. If that approach will not work, then the more complex solution
// proposed above may be necessary.
UIEdgeInsets contentInset = scrollView.contentInset;
contentInset.bottom = navigationBarHeight;
scrollView.contentInset = contentInset;
scrollView.scrollIndicatorInsets = contentInset;
}
}
}
/**
Deselects the currently set item. Sets the selectedIndex to NSNotFound, the naviagation bar's
selected item to nil, and the selectedViewController to nil.
*/
- (void)deselectCurrentItem {
_selectedIndex = NSNotFound;
self.navigationBar.selectedItem = nil;
// Force removal of the currently selected viewcontroller if there is one.
self.selectedViewController = nil;
}
/**
Sets the selected view controller to the corresponding index and updates the navigation bar's
selected item.
*/
- (void)updateViewsForSelectedIndex:(NSUInteger)index {
// Update the selected view controller
UIViewController *selectedViewController = [self.viewControllers objectAtIndex:index];
self.selectedViewController = selectedViewController;
// Update the navigation bar's selected item.
self.navigationBar.selectedItem = selectedViewController.tabBarItem;
[self setNeedsStatusBarAppearanceUpdate];
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
/**
Hooks up the constraints for the subviews of this controller. Namely the content view and the
navigation bar.
*/
- (void)loadConstraints {
[self loadConstraintsForNavigationBar];
[self loadConstraintsForContentContainerView];
}
- (void)loadConstraintsForNavigationBar {
self.navigationBar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view.leftAnchor constraintEqualToAnchor:self.navigationBar.leftAnchor].active = YES;
[self.view.rightAnchor constraintEqualToAnchor:self.navigationBar.rightAnchor].active = YES;
self.navigationBarBottomAnchorConstraint =
[self.navigationBar.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor];
self.navigationBarBottomAnchorConstraint.active = YES;
if (@available(iOS 11.0, *)) {
self.navigationBarItemsBottomAnchorConstraint = [self.navigationBar.barItemsBottomAnchor
constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor];
self.navigationBarItemsBottomAnchorConstraint.active = YES;
}
}
- (void)loadConstraintsForContentContainerView {
self.content.translatesAutoresizingMaskIntoConstraints = NO;
[self.view.leftAnchor constraintEqualToAnchor:self.content.leftAnchor].active = YES;
[self.view.rightAnchor constraintEqualToAnchor:self.content.rightAnchor].active = YES;
[self.view.topAnchor constraintEqualToAnchor:self.content.topAnchor].active = YES;
[self.view.bottomAnchor constraintEqualToAnchor:self.content.bottomAnchor].active = YES;
}
/**
Pins the given view to the edges of the content view.
*/
- (void)addConstraintsForChildViewControllerView:(UIView *)view {
view.translatesAutoresizingMaskIntoConstraints = NO;
[view.leadingAnchor constraintEqualToAnchor:self.content.leadingAnchor].active = YES;
[view.trailingAnchor constraintEqualToAnchor:self.content.trailingAnchor].active = YES;
[view.topAnchor constraintEqualToAnchor:self.content.topAnchor].active = YES;
[view.bottomAnchor constraintEqualToAnchor:self.content.bottomAnchor].active = YES;
}
/** Maps an array of view controllers to their corrisponding tab bar items. */
- (NSArray<UITabBarItem *> *)tabBarItemsForViewControllers:
(NSArray<UIViewController *> *)viewControllers {
NSMutableArray<UITabBarItem *> *tabBarItems = [NSMutableArray array];
for (UIViewController *viewController in viewControllers) {
UITabBarItem *tabBarItem = viewController.tabBarItem;
NSAssert(tabBarItem != nil,
@"%@'s tabBarItem is nil. Please ensure that each view controller "
"added to %@ has set its tab bar item property",
viewController, NSStringFromClass([self class]));
if (tabBarItem) {
[tabBarItems addObject:tabBarItem];
}
}
return tabBarItems;
}
/**
Returns an exception for when the navigation bar's items are changed from outside of this class.
*/
- (NSException *)unauthorizedItemsChangedException {
NSString *reason = [NSString
stringWithFormat:
@"Attempting to set %@'s navigation bar items. Please instead use setViewControllers:",
NSStringFromClass([self class])];
return [NSException exceptionWithName:NSInternalInconsistencyException
reason:reason
userInfo:nil];
}
/** Adds the large item dialog to the view hierarchy and animates its presentation. */
- (void)showLargeItemDialog {
if (self.largeItemDialog.superview) {
return;
}
self.largeItemDialog.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.largeItemDialog];
UIWindow *window = self.largeItemDialog.window;
[self.largeItemDialog.heightAnchor constraintEqualToConstant:kLargeItemViewHeight].active = YES;
[self.largeItemDialog.widthAnchor constraintEqualToConstant:kLargeItemViewWidth].active = YES;
[self.largeItemDialog.centerXAnchor constraintEqualToAnchor:window.centerXAnchor].active = YES;
[self.largeItemDialog.centerYAnchor constraintEqualToAnchor:window.centerYAnchor].active = YES;
self.largeItemDialog.layer.opacity = 0;
self.largeItemDialog.transform = LargeItemViewAnimationTransitionTransform();
[UIView animateWithDuration:kLargeItemViewAnimationDuration
animations:^{
self.largeItemDialog.layer.opacity = 1;
self.largeItemDialog.transform = CGAffineTransformIdentity;
}];
}
/** Removes the large item dialog from the view hierarchy and animates its dismissal. */
- (void)dismissLargeItemDialog {
if (!self.largeItemDialog.superview || self.isDismissingLargeItemDialog) {
return;
}
self.dismissingLargeItemView = YES;
[UIView animateWithDuration:kLargeItemViewAnimationDuration
animations:^{
self.largeItemDialog.layer.opacity = 0;
self.largeItemDialog.transform = LargeItemViewAnimationTransitionTransform();
}
completion:^(BOOL finished) {
if (finished) {
[self.largeItemDialog removeFromSuperview];
}
self.dismissingLargeItemView = NO;
}];
}
- (BOOL)isNavigationBarLongPressRecognizerRegistered {
return
[self.navigationBar.gestureRecognizers containsObject:self.navigationBarLongPressRecognizer];
}
/** Returns if the receiver's size category is an accessibility category. */
- (BOOL)isContentSizeCategoryAccessibilityCategory {
UIContentSizeCategory sizeCategory = UIContentSizeCategoryLarge;
sizeCategory = self.traitCollection.preferredContentSizeCategory;
if (@available(iOS 11.0, *)) {
return UIContentSizeCategoryIsAccessibilityCategory(sizeCategory);
}
return [sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityMedium] ||
[sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityLarge] ||
[sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge] ||
[sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge] ||
[sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge];
}
@end