Skip to content
Open
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
38 changes: 26 additions & 12 deletions nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

87 changes: 80 additions & 7 deletions scripts/nitrogen-postprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ const COMPONENT_FILE = join(
ROOT,
'nitrogen/generated/shared/c++/views/HybridRiveViewComponent.cpp'
);
const IOS_COMPONENT_FILE = join(
ROOT,
'nitrogen/generated/ios/c++/views/HybridRiveViewComponent.mm'
);

function makeHybridRiveViewManagerOpen() {
if (!existsSync(MANAGER_FILE)) {
Expand Down Expand Up @@ -44,6 +48,10 @@ function acceptNullForOptionalProps() {
}

const content = readFileSync(COMPONENT_FILE, 'utf-8');
if (content.includes('value.isNull()')) {
console.log('HybridRiveViewComponent.cpp already accepts null props');
return;
}
const pattern =
/^( *)return (CachedProp<std::optional<.+>>)::fromRawValue\(\*runtime, value, (sourceProps\.\w+)\);$/gm;
const updated = content.replace(
Expand All @@ -53,13 +61,9 @@ function acceptNullForOptionalProps() {
);

if (content === updated) {
if (content.includes('value.isNull()')) {
console.log('HybridRiveViewComponent.cpp already accepts null props');
} else {
console.warn(
'No optional CachedProp parse sites found in HybridRiveViewComponent.cpp — nitrogen output may have changed shape'
);
}
console.warn(
'No optional CachedProp parse sites found in HybridRiveViewComponent.cpp — nitrogen output may have changed shape'
);
return;
}

Expand All @@ -69,5 +73,74 @@ function acceptNullForOptionalProps() {
);
}

// Fabric can recreate the iOS component view from an unchanged ShadowNode
// (e.g. react-freeze / Suspense re-inserting a previously hidden screen), but
// the cached props' isDirty flags were already consumed by the previous view
// instance (they live on the shared Props object and are mutated on first
// apply), so updateProps would apply nothing and the fresh view would stay
// unconfigured: no file, no artboard, and a hybridRef that never fires.
// Force-apply every prop on a view instance's first updateProps.
// Fixed upstream in nitro 0.37 (props are diffed against oldProps instead of
// mutating shared isDirty state) — remove on the next nitro upgrade:
// https://github.com/mrousavy/nitro/pull/1506
const IVAR_BLOCK = ` // Fabric can recreate this component view from an unchanged ShadowNode
// (e.g. react-freeze / Suspense re-inserting a previously hidden screen).
// The cached props' isDirty flags were already consumed by the previous
// view instance (they live on the shared Props object and are mutated on
// first apply), so updateProps would apply nothing and the fresh
// HybridRiveView would stay unconfigured: no file, no artboard, and a
// hybridRef that never fires (JS keeps a ref to the dead old hybrid).
// Force-apply every prop on this instance's first updateProps.
BOOL _didApplyInitialProps;
`;

const FORCE_BLOCK = `
// Force-apply all props the first time this view instance updates (see
// _didApplyInitialProps above).
BOOL force = !_didApplyInitialProps;
_didApplyInitialProps = YES;
`;

function forceApplyPropsOnFirstUpdate() {
if (!existsSync(IOS_COMPONENT_FILE)) {
console.warn('HybridRiveViewComponent.mm not found, skipping');
return;
}

const content = readFileSync(IOS_COMPONENT_FILE, 'utf-8');
if (content.includes('_didApplyInitialProps')) {
console.log(
'HybridRiveViewComponent.mm already force-applies initial props'
);
return;
}

const ivarAnchor =
' std::shared_ptr<HybridRiveViewSpecSwift> _hybridView;\n';
const updateAnchor = '\n // 2. Update each prop individually\n';
const dirtyPattern = /if \(newViewProps\.(\w+)\.isDirty\)/g;
if (
!content.includes(ivarAnchor) ||
!content.includes(updateAnchor) ||
!dirtyPattern.test(content)
) {
console.warn(
'Anchors for the force-apply patch not found in HybridRiveViewComponent.mm — nitrogen output may have changed shape'
);
return;
}

const updated = content
.replace(ivarAnchor, `${ivarAnchor}${IVAR_BLOCK}`)
.replace(updateAnchor, `${FORCE_BLOCK}${updateAnchor}`)
.replace(dirtyPattern, 'if (force || newViewProps.$1.isDirty)');

writeFileSync(IOS_COMPONENT_FILE, updated);
console.log(
'Patched HybridRiveViewComponent.mm to force-apply props on first update'
);
}

makeHybridRiveViewManagerOpen();
acceptNullForOptionalProps();
forceApplyPropsOnFirstUpdate();