Skip to content

Emit the iOS launch image set whole - #320

Open
unlocdavid wants to merge 2 commits into
NativePHP:mainfrom
unlocdavid:feature/ios-launch-image-emitted-whole
Open

Emit the iOS launch image set whole#320
unlocdavid wants to merge 2 commits into
NativePHP:mainfrom
unlocdavid:feature/ios-launch-image-emitted-whole

Conversation

@unlocdavid

Copy link
Copy Markdown
Contributor

Follow-up to #159, which was closed with an invitation to raise anything in core that made building the plugin harder. The splash behavior now lives in unlocnl/nativephp-enhanced-splash; this is one of the things that turned up while building it.

It stands on its own as a bug, though — no plugin needed to hit it.

installIosSplashScreen() writes Contents.json and copies the app's splash variants into LaunchImage.imageset, but never clears the directory first. The set that ends up in the build is therefore the union of every build that came before it.

Two consequences:

  1. A dropped variant keeps shipping. Remove public/splash-dark@3x.png and rebuild: it is gone from Contents.json, but the file is still in the image set and still in the app.

  2. Content core did not write survives into a set that no longer lists it. This is the one that bites plugins. A plugin that substitutes a vector launch image writes splash.svg into the set; the next build rewrites Contents.json to list the PNGs again and leaves the .svg sitting there. That is exactly the mixed bitmap / "Any" scale combination the code immediately above this goes out of its way to avoid:

error: Image set has a child with bitmap content and the "Any" scale. The image set also has children with specific scales...

The change

Clear the image set before writing it, so it is emitted whole.

Only when there is artwork to replace it with — the clear sits after the empty($foundVariants) early return, not before it. The installed project ships a default LaunchImage.imageset, and an app with no splash of its own keeps it.

Why this helps plugins

A plugin that replaces a generated asset needs to be able to put it back when its config changes. For files core installs verbatim it can copy core's own copy back. For this set there is no such source — it is generated from the app's public/splash*.png — so today a plugin has to snapshot core's output into a private directory inside the build tree on first run, purely so it can restore it later. That stash is only necessary because the next build does not start clean.

With this change it does: whatever a plugin writes into the set, core takes the set back whole on the next build, and an app with no artwork keeps the shipped default. In the plugin this came out of, it removed the entire stash mechanism.

Testing

tests/Unit/Concerns/InstallsSplashScreenTest.php — covers the dropped variant, foreign content, and the default set being left alone when the app ships no splash. The first two fail without the change.

installIosSplashScreen() writes Contents.json and copies in the app's
splash variants, but never clears what was there first, so the set is the
union of every build that came before it. A variant the app has dropped
keeps shipping, and a file written by something other than core survives
into a set that no longer lists it — including the bitmap-beside-"Any"
scale combination the asset compiler rejects, which the code immediately
above already goes out of its way to avoid.

Clear the set before writing it. Only when there is artwork to replace it
with: the installed project ships a default set, and an app with no
splash of its own keeps it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@shanerbaner82 shanerbaner82 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice, focused fix. Two cleanup boundaries need handling before merge:

  • Guard a symlinked LaunchImage.imageset; recursive deletion can erase the link target.
  • Cover custom → no splash, which currently leaves the previous custom image shipping.

Once those edges are covered, this should be straightforward to merge.

@unlocdavid
unlocdavid force-pushed the feature/ios-launch-image-emitted-whole branch from 768f876 to 6977942 Compare August 14, 2026 13:56
@unlocdavid

Copy link
Copy Markdown
Contributor Author

Both edges are covered.

Symlinked set. File::deleteDirectory() gates on is_dir(), which resolves the link — it guards nested symlinks but not the top-level path, so the target's contents would go. Clearing now unlinks a symlinked set instead of recursing through it; the target is untouched and core gets a real directory back.

Custom → no splash. The early return on "no artwork found" was leaving the previous custom image shipping forever. That path now restores the set a fresh install ships, from resources/xcode/NativePHP/Assets.xcassets/LaunchImage.imageset. Clearing to empty was not an option — LaunchScreen.storyboard and SplashView.swift both reference the LaunchImage asset.

Two tests added for these; both fail against the previous revision (the symlink one shows the target's file erased). Full unit suite green.

LaunchImage.imageset may be a symlink into artwork the app maintains
elsewhere. File::deleteDirectory() gates on is_dir(), which resolves the
link — it guards nested symlinks but not the top-level path, so clearing
through one erases the target's contents. Unlink the link instead, and
give core a real directory back.

An app that drops its splash artwork keeps shipping the last custom
image, because the "no variants found" path returns before the set is
touched. That path now restores the set a fresh install ships. Clearing
to nothing is not an option: LaunchScreen.storyboard and SplashView.swift
both reference the LaunchImage asset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@unlocdavid
unlocdavid force-pushed the feature/ios-launch-image-emitted-whole branch from 6977942 to 44586fc Compare August 14, 2026 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants