Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.view.View
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.common.UIManagerType
import com.facebook.react.uimanager.events.Event
import com.facebook.react.views.view.ReactViewGroup
import com.swmansion.gesturehandler.core.GestureHandler
Expand Down Expand Up @@ -103,7 +104,7 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
for (tag in newHandlers) {
handlersToDetach.remove(tag)
if (!attachedHandlers.contains(tag)) {
if (shouldAttachGestureToChildView(tag)) {
if (shouldAttachGestureToChildView(tag) && actionType != GestureHandler.ACTION_TYPE_LOGIC_DETECTOR) {
// It might happen that `attachHandlers` will be called before children are added into view hierarchy. In that case we cannot
// attach `NativeViewGestureHandlers` here and we have to do it in `addView` method.
nativeHandlers.add(tag)
Expand Down Expand Up @@ -183,4 +184,8 @@ class RNGestureHandlerDetectorView(context: Context) : ReactViewGroup(context) {
}.filterNotNull()

private fun ReadableArray.toIntList(): List<Int> = List(size()) { getInt(it) }
fun getViewByReactTag(reactTag: Int): View? {
val uiManager = UIManagerHelper.getUIManager(reactContext, UIManagerType.FABRIC)
return uiManager?.resolveView(reactTag)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, why did the order of attach/detach change?

Copy link
Contributor Author

@akwasniewski akwasniewski Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you follow the logic in updateLogicDetector closely you find out that if a specific handler was previously attached to another view, which was then detached (like in the example) - due to how handlers are stored - they would first be attached to the new view and then detached completely while looping through logicChildrenToDetach. I thought that it also happens in attachHandlers, but after careful testing it is not the case. I also fixed it on android today. For some reason I can't replicate it on web, I have to dig into that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what is updateLogicDetector.

Copy link
Contributor Author

@akwasniewski akwasniewski Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant updateLogicChildren, sorry

Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,19 @@ - (void)attachHandlers:(const std::vector<int> &)handlerTags
react_native_assert(handlerManager != nullptr && "Tried to access a non-existent handler manager")

NSMutableSet *handlersToDetach = [attachedHandlers mutableCopy];

for (const int tag : handlerTags) {
[handlersToDetach removeObject:@(tag)];
}

for (const id tag : handlersToDetach) {
[handlerManager.registry detachHandlerWithTag:tag];
[attachedHandlers removeObject:tag];
[_nativeHandlers removeObject:tag];
}

for (const int tag : handlerTags) {
if (![attachedHandlers containsObject:@(tag)]) {
if ([self shouldAttachGestureToSubview:@(tag)]) {
if ([self shouldAttachGestureToSubview:@(tag)] && actionType != RNGestureHandlerActionTypeLogicDetector) {
// It might happen that `attachHandlers` will be called before children are added into view hierarchy. In that
// case we cannot attach `NativeViewGestureHandlers` here and we have to do it in `didAddSubview` method.
[_nativeHandlers addObject:@(tag)];
Expand All @@ -180,12 +188,6 @@ - (void)attachHandlers:(const std::vector<int> &)handlerTags
}
}

for (const id tag : handlersToDetach) {
[handlerManager.registry detachHandlerWithTag:tag];
[attachedHandlers removeObject:tag];
[_nativeHandlers removeObject:tag];
}

// This covers the case where `NativeViewGestureHandlers` are attached after child views were created.
if (self.subviews[0]) {
[self tryAttachNativeHandlersToChildView];
Expand Down Expand Up @@ -220,22 +222,24 @@ - (void)updateLogicChildren:(const std::vector<RNGestureHandlerDetectorLogicChil
}

for (const auto &child : logicChildren) {
if (_attachedLogicHandlers.find(child.viewTag) == _attachedLogicHandlers.end()) {
_attachedLogicHandlers[child.viewTag] = [NSMutableSet set];
}

[logicChildrenToDetach removeObject:@(child.viewTag)];

[self attachHandlers:child.handlerTags
actionType:RNGestureHandlerActionTypeLogicDetector
viewTag:child.viewTag
attachedHandlers:_attachedLogicHandlers[child.viewTag]];
}

for (const NSNumber *tag : logicChildrenToDetach) {
for (id handlerTag : _attachedLogicHandlers[tag.intValue]) {
[handlerManager.registry detachHandlerWithTag:handlerTag];
}
_attachedLogicHandlers.erase(tag.intValue);
}

for (const auto &child : logicChildren) {
if (_attachedLogicHandlers.find(child.viewTag) == _attachedLogicHandlers.end()) {
_attachedLogicHandlers[child.viewTag] = [NSMutableSet set];
}
[self attachHandlers:child.handlerTags
actionType:RNGestureHandlerActionTypeLogicDetector
viewTag:child.viewTag
attachedHandlers:_attachedLogicHandlers[child.viewTag]];
}
}

Expand Down
44 changes: 23 additions & 21 deletions packages/react-native-gesture-handler/src/v3/LogicDetector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,38 @@ export function LogicDetector<THandlerData, TConfig>(
const [viewTag, setViewTag] = useState<number>(-1);
const logicMethods = useRef(props.gesture.detectorCallbacks);

const handleRef = useCallback((node: any) => {
viewRef.current = node;
if (!node) {
return;
}
const handleRef = useCallback(
(node: any) => {
viewRef.current = node;
if (!node) {
return;
}
let tag: number | null = null;
if (Platform.OS === 'web') {
tag = node;
} else {
tag = findNodeHandle(node);
}

if (Platform.OS === 'web') {
setViewTag(node);
} else {
const tag = findNodeHandle(node);
if (tag != null) {
console.log('register ', tag);
setViewTag(tag);
}
}
}, []);

return () => {
if (tag != null) {
console.log('unregister ', tag);
unregister(viewTag);
}
};
},
[props.children]
);

useEffect(() => {
logicMethods.current = props.gesture.detectorCallbacks;
}, [props.gesture.detectorCallbacks]);

useEffect(() => {
if (viewTag === -1) {
return;
}

// Native Detector differentiates Logic Children through a viewTag,
// thus if viewTag changes we have to reregister
unregister(viewTag);
}, [viewTag]);

useEffect(() => {
if (viewTag === -1) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
if (
RNGestureHandlerModule.getGestureHandlerNode(
tag
).shouldAttachGestureToChildView()
).shouldAttachGestureToChildView() &&
actionType !== ActionType.LOGIC_DETECTOR
) {
RNGestureHandlerModule.attachGestureHandler(
tag,
Expand Down
Loading