Start at the runtime contract from lesson 03: image is float32 NCHW [1, 1, 28, 28], pixels are in 0...1, and logits has ten values. The SwiftUI screen is a thin helper. It gets a photo, turns it into those bytes, and shows the result. The screen does not change the model.
Read the picker backward
Resize and draw the chosen image into a 28×28 grayscale buffer. Scale channel values to float32 in 0...1. Add the batch and channel dimensions without swapping them. The result is not “an image for Core AI”. It is the exact NDArray contract described by the original course's SwiftUI clothing app lesson.
Sort the bugs by layer
- A colour image sent as three channels breaks the model input contract.
- A 224×224 resize breaks the fixed shape that conversion used.
- Bytes left in 0...255 break the value range that training learned.
- NHWC data passed as NCHW scrambles the buffer even if the count is right.
- Running before preparation finishes breaks the runtime lifecycle (the prepare-once rule).
You can see these failures in the UI, but the fixes belong to the contract. Do not hide them with a different label list or a larger model. Check the array shape, element type, layout, and values first.
The label also sits after the model
Read the ten logits, pick the highest score, and use the same Fashion-MNIST class order used during training: T-shirt/top, Trouser, Pullover, Dress, Coat, Sandal, Shirt, Sneaker, Bag, Ankle boot. Softmax is useful if you want to show confidence, but you do not need it to pick the winner.
Now move below the runtime to the file that made this contract possible. Conversion exists so the runtime can run. Compare it with the original conversion lesson.
Key concepts
- SwiftUI is a thin client. PhotosPicker and preprocessing exist to meet the tensor contract.
- The path is photo to 28x28 grayscale to float32 NCHW 0-1 to
imagetologits. - Wrong channels, 0-255 bytes, or infer before prepare are contract breaks.
- The ten logits become names only through the fixed Fashion-MNIST order.
Takeaways
- UI bugs that change the tensor are runtime contract breaks, not model bugs.
- Keep a known bundled sample to test preprocessing before you change the view.
- Thin UI means you can isolate prep and the runtime call from layout code.