react-native/React/Modules/RCTAccessibilityManager.m

197 lines
6.7 KiB
Objective-C

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTAccessibilityManager.h"
#import "RCTUIManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification = @"RCTAccessibilityManagerDidUpdateMultiplierNotification";
@interface RCTAccessibilityManager ()
@property (nonatomic, copy) NSString *contentSizeCategory;
@property (nonatomic, assign) CGFloat multiplier;
@end
@implementation RCTAccessibilityManager
@synthesize bridge = _bridge;
@synthesize multipliers = _multipliers;
RCT_EXPORT_MODULE()
+ (NSDictionary<NSString *, NSString *> *)JSToUIKitMap
{
static NSDictionary *map = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
map = @{@"extraSmall": UIContentSizeCategoryExtraSmall,
@"small": UIContentSizeCategorySmall,
@"medium": UIContentSizeCategoryMedium,
@"large": UIContentSizeCategoryLarge,
@"extraLarge": UIContentSizeCategoryExtraLarge,
@"extraExtraLarge": UIContentSizeCategoryExtraExtraLarge,
@"extraExtraExtraLarge": UIContentSizeCategoryExtraExtraExtraLarge,
@"accessibilityMedium": UIContentSizeCategoryAccessibilityMedium,
@"accessibilityLarge": UIContentSizeCategoryAccessibilityLarge,
@"accessibilityExtraLarge": UIContentSizeCategoryAccessibilityExtraLarge,
@"accessibilityExtraExtraLarge": UIContentSizeCategoryAccessibilityExtraExtraLarge,
@"accessibilityExtraExtraExtraLarge": UIContentSizeCategoryAccessibilityExtraExtraExtraLarge};
});
return map;
}
+ (NSString *)UIKitCategoryFromJSCategory:(NSString *)JSCategory
{
return [self JSToUIKitMap][JSCategory];
}
- (instancetype)init
{
if ((self = [super init])) {
// TODO: can this be moved out of the startup path?
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveNewContentSizeCategory:)
name:UIContentSizeCategoryDidChangeNotification
object:RCTSharedApplication()];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveNewVoiceOverStatus:)
name:UIAccessibilityVoiceOverStatusChanged
object:nil];
self.contentSizeCategory = RCTSharedApplication().preferredContentSizeCategory;
_isVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning();
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)didReceiveNewContentSizeCategory:(NSNotification *)note
{
self.contentSizeCategory = note.userInfo[UIContentSizeCategoryNewValueKey];
}
- (void)didReceiveNewVoiceOverStatus:(__unused NSNotification *)notification
{
BOOL newIsVoiceOverEnabled = UIAccessibilityIsVoiceOverRunning();
if (_isVoiceOverEnabled != newIsVoiceOverEnabled) {
_isVoiceOverEnabled = newIsVoiceOverEnabled;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[_bridge.eventDispatcher sendDeviceEventWithName:@"voiceOverDidChange"
body:@(_isVoiceOverEnabled)];
#pragma clang diagnostic pop
}
}
- (void)setContentSizeCategory:(NSString *)contentSizeCategory
{
if (_contentSizeCategory != contentSizeCategory) {
_contentSizeCategory = [contentSizeCategory copy];
[self invalidateMultiplier];
}
}
- (void)invalidateMultiplier
{
self.multiplier = [self multiplierForContentSizeCategory:_contentSizeCategory];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTAccessibilityManagerDidUpdateMultiplierNotification object:self];
}
- (CGFloat)multiplierForContentSizeCategory:(NSString *)category
{
NSNumber *m = self.multipliers[category];
if (m.doubleValue <= 0.0) {
RCTLogError(@"Can't determinte multiplier for category %@. Using 1.0.", category);
m = @1.0;
}
return m.doubleValue;
}
- (void)setMultipliers:(NSDictionary<NSString *, NSNumber *> *)multipliers
{
if (_multipliers != multipliers) {
_multipliers = [multipliers copy];
[self invalidateMultiplier];
}
}
- (NSDictionary<NSString *, NSNumber *> *)multipliers
{
if (_multipliers == nil) {
_multipliers = @{UIContentSizeCategoryExtraSmall: @0.823,
UIContentSizeCategorySmall: @0.882,
UIContentSizeCategoryMedium: @0.941,
UIContentSizeCategoryLarge: @1.0,
UIContentSizeCategoryExtraLarge: @1.118,
UIContentSizeCategoryExtraExtraLarge: @1.235,
UIContentSizeCategoryExtraExtraExtraLarge: @1.353,
UIContentSizeCategoryAccessibilityMedium: @1.786,
UIContentSizeCategoryAccessibilityLarge: @2.143,
UIContentSizeCategoryAccessibilityExtraLarge: @2.643,
UIContentSizeCategoryAccessibilityExtraExtraLarge: @3.143,
UIContentSizeCategoryAccessibilityExtraExtraExtraLarge: @3.571};
}
return _multipliers;
}
RCT_EXPORT_METHOD(setAccessibilityContentSizeMultipliers:(NSDictionary *)JSMultipliers)
{
NSMutableDictionary<NSString *, NSNumber *> *multipliers = [NSMutableDictionary new];
for (NSString *__nonnull JSCategory in JSMultipliers) {
NSNumber *m = [RCTConvert NSNumber:JSMultipliers[JSCategory]];
NSString *UIKitCategory = [[self class] UIKitCategoryFromJSCategory:JSCategory];
multipliers[UIKitCategory] = m;
}
self.multipliers = multipliers;
}
RCT_EXPORT_METHOD(setAccessibilityFocus:(nonnull NSNumber *)reactTag)
{
dispatch_async(dispatch_get_main_queue(), ^{
UIView *view = [self.bridge.uiManager viewForReactTag:reactTag];
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, view);
});
}
RCT_EXPORT_METHOD(getMultiplier:(RCTResponseSenderBlock)callback)
{
if (callback) {
callback(@[ @(self.multiplier) ]);
}
}
RCT_EXPORT_METHOD(getCurrentVoiceOverState:(RCTResponseSenderBlock)callback
error:(__unused RCTResponseSenderBlock)error)
{
callback(@[@(_isVoiceOverEnabled)]);
}
@end
@implementation RCTBridge (RCTAccessibilityManager)
- (RCTAccessibilityManager *)accessibilityManager
{
return [self moduleForClass:[RCTAccessibilityManager class]];
}
@end