ReviewOS

craft-native/craft

iOS Image object-fit paints the original UIImageView image behind its fitted child

#441
Open glennmichael123 opened this 5 hours ago ·
5 hours ago

Observed

The iOS Image object-fit implementation computes the correct fitted child frame, but the parent UIImageView also paints the stored source image underneath it. This leaves a larger unwanted image behind none/scale-down content.

Found during stacksjs/stx#2091 native Image acceptance. STX's separate containing-box and initial-remount-payload bugs are being fixed in STX. This pixel failure reproduces by constructing CraftNativePositionedImageView directly, with no STX compiler, runtime, bridge, or component involved.

Reproduction and evidence

Clean Craft commit: 1e141f1d3a02c9e5798cb3d816bcffc2d0d66d55 (SDK source version 0.0.424). Generated simulator app using the existing SDK; Craft checkout/templates unchanged. Xcode 26.6 (17F113), owned iPhone 17 Pro simulator / iOS 26.5 (23F77).

  1. Build a 20×10 UIImage with opaque red/blue halves.
  2. Place a CraftNativePositionedImageView in a 100×100 box, set contentMode = .scaleAspectFit, configure objectFit: scale-down, objectPosition: right bottom, and set image to that source.
  3. Its fitted child frame is correctly (80, 90, 20, 10).
  4. Capture the actual view with drawHierarchy(in:afterScreenUpdates:) at scale 1 and measure the red/blue pixel bounds.

Control: an ordinary UIView containing one UIImageView at that same fitted frame paints (80, 90, 20, 10). Craft view: paints (0, 26, 100, 74). Both the larger contain-sized image and the small correctly positioned image are visible. The test retains both raster attachments and fails the equality assertion.

Retained local result: /var/folders/sq/p6y46h2x6ddgq6w1yyxhd_9h0000gn/T/stx-portability-ios-Xzftc1/RawRasterProbe.xcresult (1 failed, 0 skipped). This is a pixel assertion failure; the control and child-frame assertions pass.

The relevant implementation is packages/ios/templates/CraftNativeScreen.swift, CraftNativePositionedImageView: its getter returns storedImage, while configuration tries to clear super.image and draws another UIImageView child. The raster suggests UIImageView still renders through that overridden getter. That cause is an inference; the duplicated pixels are measured directly.

Expected

Only the fitted image paints when object geometry is active. Keep the public stored image/decode identity, source/load ownership, crop/position behavior and unchanged-source updates intact. Clearing explicit geometry should restore the legacy path exactly once. Test actual pixels as well as child frames, including fit transitions and source/remount changes.

Follows #427: the geometry contract exists, but frame-only tests missed the duplicate paint. STX will retain this as an explicit native acceptance gate and link the maintained reproducer once pushed. Craft code remains with its assigned agent; no host fix or workaround was applied here.

5 hours ago

The strict reproducer is now maintained in STX main, commit 1309815; Craft source remains untouched:

Both light and dark final runs against clean Craft 1e141f1d3a02c9e5798cb3d816bcffc2d0d66d55 report 5 passed / 1 failed / 0 skipped; this raster assertion is the only failure. The gate requires all six to pass; nothing is skipped or marked as an expected failure. Separate STX sizing/remount defects have been fixed without changing the host. The remaining drawing fix belongs here.

Sign in to comment on this issue.