/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #import "UIView+React.h" #import #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); } - (NSNumber *)rootTag { return objc_getAssociatedObject(self, _cmd); } - (void)setRootTag:(NSNumber *)rootTag { objc_setAssociatedObject(self, @selector(rootTag), rootTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)nativeID { return objc_getAssociatedObject(self, _cmd); } - (void)setNativeID:(NSString *)nativeID { objc_setAssociatedObject(self, @selector(nativeID), nativeID, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (BOOL)shouldAccessibilityIgnoresInvertColors { return self.accessibilityIgnoresInvertColors; } - (void)setShouldAccessibilityIgnoresInvertColors:(BOOL)shouldAccessibilityIgnoresInvertColors { self.accessibilityIgnoresInvertColors = shouldAccessibilityIgnoresInvertColors; } - (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 *)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]; } #pragma mark - Display - (YGDisplay)reactDisplay { return self.isHidden ? YGDisplayNone : YGDisplayFlex; } - (void)setReactDisplay:(YGDisplay)display { self.hidden = display == YGDisplayNone; } #pragma mark - Layout Direction - (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); } } #pragma mark - zIndex - (NSInteger)reactZIndex { return self.layer.zPosition; } - (void)setReactZIndex:(NSInteger)reactZIndex { self.layer.zPosition = reactZIndex; } - (NSArray *)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)didSetProps:(__unused NSArray *)changedProps { // The default implementation does nothing. } - (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; } - (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 - Accessibility - (UIView *)reactAccessibilityElement { return self; } - (NSArray *)accessibilityActions { return objc_getAssociatedObject(self, _cmd); } - (void)setAccessibilityActions:(NSArray *)accessibilityActions { objc_setAssociatedObject( self, @selector(accessibilityActions), accessibilityActions, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)accessibilityLanguage { return objc_getAssociatedObject(self, _cmd); } - (void)setAccessibilityLanguage:(NSString *)accessibilityLanguage { objc_setAssociatedObject( self, @selector(accessibilityLanguage), accessibilityLanguage, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)accessibilityRole { return objc_getAssociatedObject(self, _cmd); } - (void)setAccessibilityRole:(NSString *)accessibilityRole { objc_setAssociatedObject(self, @selector(accessibilityRole), accessibilityRole, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSDictionary *)accessibilityState { return objc_getAssociatedObject(self, _cmd); } - (void)setAccessibilityState:(NSDictionary *)accessibilityState { objc_setAssociatedObject(self, @selector(accessibilityState), accessibilityState, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSDictionary *)accessibilityValueInternal { return objc_getAssociatedObject(self, _cmd); } - (void)setAccessibilityValueInternal:(NSDictionary *)accessibilityValue { objc_setAssociatedObject( self, @selector(accessibilityValueInternal), accessibilityValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } #pragma mark - Debug - (void)react_addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level { for (NSUInteger i = 0; i < level; i++) { [string appendString:@" | "]; } [string appendString:self.description]; [string appendString:@"\n"]; for (UIView *subview in self.subviews) { [subview react_addRecursiveDescriptionToString:string atLevel:level + 1]; } } - (NSString *)react_recursiveDescription { NSMutableString *description = [NSMutableString string]; [self react_addRecursiveDescriptionToString:description atLevel:0]; return description; } @end