Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion React/Base/RCTUIKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ CGPathRef UIBezierPathCreateCGPathRef(UIBezierPath *path);
- (void)layoutIfNeeded;

- (void)layoutSubviews;
- (NSArray<RCTUIView *> *)reactZIndexSortedSubviews; // [macOS]

- (void)setNeedsDisplay;

Expand All @@ -410,7 +411,10 @@ CGPathRef UIBezierPathCreateCGPathRef(UIBezierPath *path);
* Specifies whether focus ring should be drawn when the view has the first responder status.
*/
@property (nonatomic, assign) BOOL enableFocusRing;

/**
* The z-index of the view.
*/
@property (nonatomic, assign) NSInteger reactZIndex;

@end

Expand Down
21 changes: 21 additions & 0 deletions React/Base/macOS/RCTUIKit.m
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,27 @@ - (void)layoutSubviews
[super layout];
}

- (NSArray<RCTUIView *> *)reactZIndexSortedSubviews
{
// Check if sorting is required - in most cases it won't be.
BOOL sortingRequired = NO;
for (RCTUIView *subview in self.subviews) {
if (subview.reactZIndex != 0) {
sortingRequired = YES;
break;
}
}
return sortingRequired ? [self.subviews sortedArrayUsingComparator:^NSComparisonResult(RCTUIView *a, RCTUIView *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)setNeedsDisplay
{
self.needsDisplay = YES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ - (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(NSSet<NSString *
return _propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN;
}

#if !TARGET_OS_OSX // [macOS]
- (RCTUIView *)betterHitTest:(CGPoint)point withEvent:(UIEvent *)event // [macOS]
{
// This is a classic textbook implementation of `hitTest:` with a couple of improvements:
Expand Down Expand Up @@ -497,6 +498,60 @@ - (RCTUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event // [macOS]
return view != self ? view : nil;
}
}
#else // [macOS

- (RCTUIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event // [macOS]
{
BOOL canReceiveTouchEvents = ([self isUserInteractionEnabled] && ![self isHidden]);
if (!canReceiveTouchEvents) {
return nil;
}

// `hitSubview` is the topmost subview which was hit. The hit point can
// be outside the bounds of `view` (e.g., if -clipsToBounds is NO).
RCTUIView *hitSubview = nil; // [macOS]
BOOL isPointInside = [self pointInside:point withEvent:event];
BOOL needsHitSubview = !(_props->pointerEvents == PointerEventsMode::None || _props->pointerEvents == PointerEventsMode::BoxOnly);
if (needsHitSubview && (![self clipsToBounds] || isPointInside)) {
// Take z-index into account when calculating the touch target.
NSArray<RCTUIView *> *sortedSubviews = [self reactZIndexSortedSubviews]; // [macOS]

// The default behaviour of UIKit is that if a view does not contain a point,
// then no subviews will be returned from hit testing, even if they contain
// the hit point. By doing hit testing directly on the subviews, we bypass
// the strict containment policy (i.e., UIKit guarantees that every ancestor
// of the hit view will return YES from -pointInside:withEvent:). See:
// - https://developer.apple.com/library/ios/qa/qa2013/qa1812.html
for (RCTUIView *subview in [sortedSubviews reverseObjectEnumerator]) { // [macOS]
CGPoint pointForHitTest = CGPointZero;
if ([subview isKindOfClass:[RCTUIView class]]) { // [macOS]
pointForHitTest = [subview convertPoint:point fromView:self];
} else {
pointForHitTest = point;
}
hitSubview = (RCTUIView *)[subview hitTest:pointForHitTest]; // [macOS]
if (hitSubview != nil) {
break;
}
}
}

RCTUIView *hitView = (isPointInside ? self : nil); // [macOS]

switch (_props->pointerEvents) {
case PointerEventsMode::None:
return nil;
case PointerEventsMode::Auto:
return hitSubview ?: hitView;
case PointerEventsMode::BoxOnly:
return hitView;
case PointerEventsMode::BoxNone:
return hitSubview;
default:
return hitSubview ?: hitView;
}
}
#endif // macOS]

static RCTCornerRadii RCTCornerRadiiFromBorderRadii(BorderRadii borderRadii)
{
Expand Down
1 change: 0 additions & 1 deletion React/Fabric/Mounting/RCTComponentViewDescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class RCTComponentViewDescriptor final {
* Associated (and owned) native view instance.
*/
__strong RCTUIView<RCTComponentViewProtocol> *view = nil; // [macOS]
NSInteger tag = 0; // [macOS] default is 0
/*
* Indicates a requirement to call on the view methods from
* `RCTMountingTransactionObserving` protocol.
Expand Down
3 changes: 3 additions & 0 deletions React/Fabric/Mounting/RCTComponentViewProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ typedef NS_OPTIONS(NSInteger, RNComponentViewUpdateMask) {
- (BOOL)isJSResponder;
- (void)setIsJSResponder:(BOOL)isJSResponder;

- (NSNumber *)reactTag; // [macOS]
- (void)setReactTag:(NSNumber *)reactTag; // [macOS]

/*
* This is broken. Do not use.
*/
Expand Down
4 changes: 2 additions & 2 deletions React/Fabric/Mounting/RCTComponentViewRegistry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ - (void)preallocateViewComponents
#if !TARGET_OS_OSX // [macOS]
componentViewDescriptor.view.tag = tag;
#else // [macOS
componentViewDescriptor.tag = tag;
componentViewDescriptor.view.reactTag = @(tag);
#endif // macOS]
auto it = _registry.insert({tag, componentViewDescriptor});
return it.first->second;
Expand All @@ -107,7 +107,7 @@ - (void)enqueueComponentViewWithComponentHandle:(ComponentHandle)componentHandle
#if !TARGET_OS_OSX // [macOS]
componentViewDescriptor.view.tag = 0;
#else // [macOS
componentViewDescriptor.tag = 0;
componentViewDescriptor.view.reactTag = @0;
#endif // macOS]
[self _enqueueComponentViewWithComponentHandle:componentHandle componentViewDescriptor:componentViewDescriptor];
}
Expand Down
3 changes: 3 additions & 0 deletions React/Fabric/Mounting/UIView+ComponentViewProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ NS_ASSUME_NONNULL_BEGIN

- (void)setIsJSResponder:(BOOL)isJSResponder;

- (NSNumber *)reactTag; // [macOS]
- (void)setReactTag:(NSNumber *)reactTag; // [macOS]

- (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(nullable NSSet<NSString *> *)props;
- (nullable NSSet<NSString *> *)propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN;

Expand Down
12 changes: 12 additions & 0 deletions React/Fabric/Mounting/UIView+ComponentViewProtocol.mm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#import "UIView+ComponentViewProtocol.h"

#import <objc/runtime.h> // [macOS]

#import <React/RCTAssert.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
Expand Down Expand Up @@ -152,6 +154,16 @@ - (void)setIsJSResponder:(BOOL)isJSResponder
// Default implementation does nothing.
}

- (NSNumber *)reactTag
{
return objc_getAssociatedObject(self, _cmd);
}

- (void)setReactTag:(NSNumber *)reactTag
{
objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (void)setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:(nullable NSSet<NSString *> *)propKeys
{
// Default implementation does nothing.
Expand Down
Loading