- TypeScript 56.4%
- Ruby 22.5%
- Swift 20.2%
- C 0.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
anyline-ocr-react-native-module 56.5.0 (same code in 56.4.0), React Native 0.81.5, Expo SDK 54, New Architecture — the same versions as the plugin's own example/RNExampleApp, plus react-navigation. No Anyline license key and no scan are involved: the app terminates while React mounts the native view. AnylineNativeViewManager.m attaches a helper UIViewController to the application's root view controller, while the container view handed to React is mounted under the current screen's view controller (RNSScreen). UIKit's hierarchy check then raises UIViewControllerHierarchyInconsistency. App.tsx offers three placements of the same component (mounted into the visible screen, mounted during the push transition, mounted at the app root) so the difference is the mount point and the moment of mounting, nothing else. patches/ contains a one-function workaround. |
||
| assets | ||
| ios | ||
| patches | ||
| .gitignore | ||
| app.json | ||
| App.tsx | ||
| index.ts | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
AnylineNativeView crash reproduction (iOS)
Minimal app that demonstrates: mounting AnylineNativeView into a react-navigation
screen that is already on screen terminates the app on iOS.
No Anyline license key and no scan are involved. The app dies while React mounts the
native view, so requestSdkInitialization and requestScanStart are never called.
*** Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency'
child view controller:<UIViewController: 0x…>
should have parent view controller:<RNSScreen: 0x…>
but actual parent is:<UIViewController: 0x…>
Versions
anyline-ocr-react-native-module |
56.5.0 (identical code in 56.4.0) |
| React Native | 0.81.5, React 19.1.0, New Architecture enabled |
| Expo | SDK 54 — same versions as example/RNExampleApp |
| Navigation | @react-navigation/native 7.2.2, native-stack 7.14.12, react-native-screens 4.16.0 |
| Verified with | Xcode 26.6, iOS 18.6 Simulator |
Run it
npm install
npx expo prebuild -p ios --clean # downloads Anyline.xcframework (~198 MB), runs pod install
npx expo run:ios # or open ios/AnylineNativeViewRepro.xcworkspace in Xcode
The three cases
The app mounts the same component in three places, so only the mount point and the moment of mounting differ.
| What it does | Result | |
|---|---|---|
| A | Open the screen, then press “Mount now” — the view is added to a screen that is already in the window | App terminates, exception above |
| B | Mount the view while the screen is being pushed | No crash |
| C | Mount the same component at the app root, outside the navigator | No crash — a grey box appears |
Two conditions have to meet for the crash:
- The container view is mounted into a hierarchy that is already in a window. During a push transition the screen is not in the window yet, so UIKit never runs its hierarchy check — case B. In a real app condition 1 is met on every re-mount: going back and re-entering the scan screen, or toggling the view for a fallback UI. That is how we ran into it on device.
- The nearest ancestor view controller is not the app's root view controller. Case C mounts under the root view controller, where the plugin's assumption happens to hold.
To see the exception text rather than a silent termination, run from Xcode with an
Objective-C exception breakpoint (breakpoint set -E objc, then po $rdi).
Cause
ios/NativeView/AnylineNativeViewManager.m in the plugin:
- (UIView *)view {
UIView *containerView = [[UIView alloc] init];
NSString *containerId = [NSString stringWithFormat:@"%d", 0];
[[NativeViewRegistry shared] registerView:containerView withId:containerId];
UIViewController *vc = [[UIViewController alloc] init];
vc.view.frame = containerView.bounds;
vc.view.backgroundColor = [UIColor lightGrayColor];
UIViewController *rootVC = UIApplication.sharedApplication.delegate.window.rootViewController;
[rootVC addChildViewController:vc]; // <-- parent hardcoded to the app's root VC
[containerView addSubview:vc.view];
[vc didMoveToParentViewController:rootVC];
return containerView;
}
React mounts containerView wherever the app places the component. With react-navigation's
native stack the owning view controller is an RNSScreen, not the application's root view
controller, so UIKit's check fails while the subtree is moved into the window:
-[UIView(Hierarchy) _associatedViewControllerForwardsAppearanceCallbacks:performHierarchyCheck:isRoot:]
-[UIView(Hierarchy) _willMoveToWindow:withAncestorView:]
_makeSubTreePerformSelector
-[UIView(Internal) _addSubview:positioned:relativeTo:]
-[RCTViewComponentView mountChildComponentView:index:]
example/RNExampleApp cannot surface this because it uses no navigation library, so all
views sit directly under the root view controller — case C above.
Workaround
Removing the helper view controller fixes it; the SDK only needs the registered container
view (-[AnylineInfinityPlugin getContainerView] → NativeViewRegistry.getLastOrNull):
- (UIView *)view {
UIView *containerView = [[UIView alloc] init];
containerView.backgroundColor = [UIColor blackColor];
NSString *containerId = [NSString stringWithFormat:@"%d", 0];
[[NativeViewRegistry shared] registerView:containerView withId:containerId];
return containerView;
}
With that change case A no longer crashes, and in our own app the embedded scan view works
normally inside a navigation screen: preview, cutout, flash button and results.
Android already behaves this way — NativeFragmentContainerView is a plain FrameLayout
that only registers itself, and it also cleans up in onDropViewInstance, which iOS does
not do at all.
Try it here:
patch -p0 < patches/AnylineNativeViewManager.m.patch # apply
npx expo run:ios
patch -R -p0 < patches/AnylineNativeViewManager.m.patch # revert
Files
| Path | What it is |
|---|---|
App.tsx |
the whole reproduction — three mount points, ~120 lines |
patches/AnylineNativeViewManager.m.patch |
the workaround, as a plain patch against node_modules |