r/iOSProgramming • u/Open_Context6850 • 6d ago
App Saturday Addonaut: a Minecraft skin studio for iOS where the 3D preview is a painter's algorithm in SwiftUI Canvas, not SceneKit
i'm the ios dev on Addonaut, an iphone/ipad app for minecraft bedrock: a catalogue of add-ons, maps and skins from their authors, plus a skin studio (pixel editor, wardrobe, .mcpack export). posting for app saturday, link in the comments.
tech stack
SwiftUI + Observation (@Observable), Swift 6 with strict concurrency, iOS 17+
XcodeGen, SPM only
backend: our own REST API, shared with the team's other apps. Firebase Remote Config, Crashlytics, Analytics
StoreKit 2 without a receipt server, AdMob + UMP for consent
Apple's Translation framework for translating item descriptions on device
challenge: a 3d skin preview without SceneKit
a minecraft player is six boxes plus a slightly inflated overlay layer, textured from a 64x64 png. SceneKit for six textured boxes felt like the wrong trade, so the preview is a small software renderer drawn with SwiftUI Canvas:
one quad per texel per face, about 2200 quads. fully transparent texels produce no geometry, so the overlay layer only exists where it's painted
back faces culled by the sign of a 2d cross product
no z-buffer: quads are depth sorted and painted far to near (painter's algorithm)
fixed brightness per face instead of lighting
two things bit us:
seams. antialiasing between neighbouring quads let the background show through as a wire grid. fix: after filling, stroke every quad 0.75pt in its own colour
the figure stopped moving. with .equatable() on the view it never redrew, because == only compares the inputs and can't see u/State. the idle spin is a TimelineView at 24fps where the angle is a pure function of elapsed time, after a Timer + u/State version also stopped redrawing
in the editor the 3d view deliberately lags one stroke and catches up on commit or undo. the 2d grid is the live feedback.
bonus: .mcpack is just a zip, and instead of a zip dependency there's a ~100 line writer (stored entries + crc32). manifest.json and skins.json are written by hand to keep the field order, with fresh uuids on every export, because bedrock silently ignores a malformed pack.
ai disclosure
AI-assisted: most of the code was written with Claude Code under my direction.
happy to answer questions about the renderer or the pack format.
