mirror of
https://github.com/status-im/react-native.git
synced 2025-01-28 02:04:55 +00:00
072d2709df
Summary: Sometimes, when we implement some custom RN view, we have to proxy all accessible atributes directly to some subview which actually has accesible content. So, in other words, this allows bypass some axillary views in terms of accessibility. Concreate example which this approach supposed to fix: https://github.com/facebook/react-native/pull/14200/files#diff-e5f6b1386b7ba07fd887bca11ec828a4R208 Reviewed By: mmmulani Differential Revision: D5143860 fbshipit-source-id: 6d7ce747f28e5a31d32c925b8ad8fd4b98ce1de1
273 lines
7.1 KiB
Objective-C
273 lines
7.1 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 "UIView+React.h"
|
|
|
|
#import <objc/runtime.h>
|
|
|
|
#import "RCTAssert.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTShadowView.h"
|
|
|
|
@implementation UIView (React)
|
|
|
|
- (NSNumber *)reactTag
|
|
{
|
|
return objc_getAssociatedObject(self, _cmd);
|
|
}
|
|
|
|
- (void)setReactTag:(NSNumber *)reactTag
|
|
{
|
|
objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
#if RCT_DEV
|
|
|
|
- (RCTShadowView *)_DEBUG_reactShadowView
|
|
{
|
|
return objc_getAssociatedObject(self, _cmd);
|
|
}
|
|
|
|
- (void)_DEBUG_setReactShadowView:(RCTShadowView *)shadowView
|
|
{
|
|
// Use assign to avoid keeping the shadowView alive it if no longer exists
|
|
objc_setAssociatedObject(self, @selector(_DEBUG_reactShadowView), shadowView, OBJC_ASSOCIATION_ASSIGN);
|
|
}
|
|
|
|
#endif
|
|
|
|
- (BOOL)isReactRootView
|
|
{
|
|
return RCTIsReactRootView(self.reactTag);
|
|
}
|
|
|
|
- (NSNumber *)reactTagAtPoint:(CGPoint)point
|
|
{
|
|
UIView *view = [self hitTest:point withEvent:nil];
|
|
while (view && !view.reactTag) {
|
|
view = view.superview;
|
|
}
|
|
return view.reactTag;
|
|
}
|
|
|
|
- (NSArray<UIView *> *)reactSubviews
|
|
{
|
|
return objc_getAssociatedObject(self, _cmd);
|
|
}
|
|
|
|
- (UIView *)reactSuperview
|
|
{
|
|
return self.superview;
|
|
}
|
|
|
|
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
|
{
|
|
// We access the associated object directly here in case someone overrides
|
|
// the `reactSubviews` getter method and returns an immutable array.
|
|
NSMutableArray *subviews = objc_getAssociatedObject(self, @selector(reactSubviews));
|
|
if (!subviews) {
|
|
subviews = [NSMutableArray new];
|
|
objc_setAssociatedObject(self, @selector(reactSubviews), subviews, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
[subviews insertObject:subview atIndex:atIndex];
|
|
}
|
|
|
|
- (void)removeReactSubview:(UIView *)subview
|
|
{
|
|
// We access the associated object directly here in case someone overrides
|
|
// the `reactSubviews` getter method and returns an immutable array.
|
|
NSMutableArray *subviews = objc_getAssociatedObject(self, @selector(reactSubviews));
|
|
[subviews removeObject:subview];
|
|
[subview removeFromSuperview];
|
|
}
|
|
|
|
- (UIUserInterfaceLayoutDirection)reactLayoutDirection
|
|
{
|
|
if ([self respondsToSelector:@selector(semanticContentAttribute)]) {
|
|
return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute];
|
|
} else {
|
|
return [objc_getAssociatedObject(self, @selector(reactLayoutDirection)) integerValue];
|
|
}
|
|
}
|
|
|
|
- (void)setReactLayoutDirection:(UIUserInterfaceLayoutDirection)layoutDirection
|
|
{
|
|
if ([self respondsToSelector:@selector(setSemanticContentAttribute:)]) {
|
|
self.semanticContentAttribute =
|
|
layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight ?
|
|
UISemanticContentAttributeForceLeftToRight :
|
|
UISemanticContentAttributeForceRightToLeft;
|
|
} else {
|
|
objc_setAssociatedObject(self, @selector(reactLayoutDirection), @(layoutDirection), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
}
|
|
|
|
- (NSInteger)reactZIndex
|
|
{
|
|
return self.layer.zPosition;
|
|
}
|
|
|
|
- (void)setReactZIndex:(NSInteger)reactZIndex
|
|
{
|
|
self.layer.zPosition = reactZIndex;
|
|
}
|
|
|
|
- (NSArray<UIView *> *)reactZIndexSortedSubviews
|
|
{
|
|
// Check if sorting is required - in most cases it won't be.
|
|
BOOL sortingRequired = NO;
|
|
for (UIView *subview in self.subviews) {
|
|
if (subview.reactZIndex != 0) {
|
|
sortingRequired = YES;
|
|
break;
|
|
}
|
|
}
|
|
return sortingRequired ? [self.reactSubviews sortedArrayUsingComparator:^NSComparisonResult(UIView *a, UIView *b) {
|
|
if (a.reactZIndex > b.reactZIndex) {
|
|
return NSOrderedDescending;
|
|
} else {
|
|
// Ensure sorting is stable by treating equal zIndex as ascending so
|
|
// that original order is preserved.
|
|
return NSOrderedAscending;
|
|
}
|
|
}] : self.subviews;
|
|
}
|
|
|
|
- (void)didUpdateReactSubviews
|
|
{
|
|
for (UIView *subview in self.reactSubviews) {
|
|
[self addSubview:subview];
|
|
}
|
|
}
|
|
|
|
- (void)reactSetFrame:(CGRect)frame
|
|
{
|
|
// These frames are in terms of anchorPoint = topLeft, but internally the
|
|
// views are anchorPoint = center for easier scale and rotation animations.
|
|
// Convert the frame so it works with anchorPoint = center.
|
|
CGPoint position = {CGRectGetMidX(frame), CGRectGetMidY(frame)};
|
|
CGRect bounds = {CGPointZero, frame.size};
|
|
|
|
// Avoid crashes due to nan coords
|
|
if (isnan(position.x) || isnan(position.y) ||
|
|
isnan(bounds.origin.x) || isnan(bounds.origin.y) ||
|
|
isnan(bounds.size.width) || isnan(bounds.size.height)) {
|
|
RCTLogError(@"Invalid layout for (%@)%@. position: %@. bounds: %@",
|
|
self.reactTag, self, NSStringFromCGPoint(position), NSStringFromCGRect(bounds));
|
|
return;
|
|
}
|
|
|
|
self.center = position;
|
|
self.bounds = bounds;
|
|
}
|
|
|
|
- (void)reactSetInheritedBackgroundColor:(__unused UIColor *)inheritedBackgroundColor
|
|
{
|
|
// Does nothing by default
|
|
}
|
|
|
|
- (UIViewController *)reactViewController
|
|
{
|
|
id responder = [self nextResponder];
|
|
while (responder) {
|
|
if ([responder isKindOfClass:[UIViewController class]]) {
|
|
return responder;
|
|
}
|
|
responder = [responder nextResponder];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)reactAddControllerToClosestParent:(UIViewController *)controller
|
|
{
|
|
if (!controller.parentViewController) {
|
|
UIView *parentView = (UIView *)self.reactSuperview;
|
|
while (parentView) {
|
|
if (parentView.reactViewController) {
|
|
[parentView.reactViewController addChildViewController:controller];
|
|
[controller didMoveToParentViewController:parentView.reactViewController];
|
|
break;
|
|
}
|
|
parentView = (UIView *)parentView.reactSuperview;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Focus manipulation.
|
|
*/
|
|
- (BOOL)reactIsFocusNeeded
|
|
{
|
|
return [(NSNumber *)objc_getAssociatedObject(self, @selector(reactIsFocusNeeded)) boolValue];
|
|
}
|
|
|
|
- (void)setReactIsFocusNeeded:(BOOL)isFocusNeeded
|
|
{
|
|
objc_setAssociatedObject(self, @selector(reactIsFocusNeeded), @(isFocusNeeded), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
}
|
|
|
|
- (void)reactFocus {
|
|
if (![self becomeFirstResponder]) {
|
|
self.reactIsFocusNeeded = YES;
|
|
}
|
|
}
|
|
|
|
- (void)reactFocusIfNeeded {
|
|
if (self.reactIsFocusNeeded) {
|
|
if ([self becomeFirstResponder]) {
|
|
self.reactIsFocusNeeded = NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)reactBlur {
|
|
[self resignFirstResponder];
|
|
}
|
|
|
|
#pragma mark - Layout
|
|
|
|
- (UIEdgeInsets)reactBorderInsets
|
|
{
|
|
CGFloat borderWidth = self.layer.borderWidth;
|
|
return UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth);
|
|
}
|
|
|
|
- (UIEdgeInsets)reactPaddingInsets
|
|
{
|
|
return UIEdgeInsetsZero;
|
|
}
|
|
|
|
- (UIEdgeInsets)reactCompoundInsets
|
|
{
|
|
UIEdgeInsets borderInsets = self.reactBorderInsets;
|
|
UIEdgeInsets paddingInsets = self.reactPaddingInsets;
|
|
|
|
return UIEdgeInsetsMake(
|
|
borderInsets.top + paddingInsets.top,
|
|
borderInsets.left + paddingInsets.left,
|
|
borderInsets.bottom + paddingInsets.bottom,
|
|
borderInsets.right + paddingInsets.right
|
|
);
|
|
}
|
|
|
|
- (CGRect)reactContentFrame
|
|
{
|
|
return UIEdgeInsetsInsetRect(self.bounds, self.reactCompoundInsets);
|
|
}
|
|
|
|
#pragma mark - Accessiblity
|
|
|
|
- (UIView *)reactAccessibilityElement
|
|
{
|
|
return self;
|
|
}
|
|
|
|
@end
|