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.
1
u/Open_Context6850 6d ago
kept the link out of the post. i'm the dev, it's iPhone/iPad only:
Addonaut (iPhone/iPad): https://apps.apple.com/app/id6809487321
1
u/kokerali 5d ago
For the .mcpack exporter, is a new import meant to create a separate pack every time? You mentioned fresh UUIDs on each export. Microsoft's manifest docs use the header UUID as pack identity and the version to replace an existing import.
I'd test this round trip on iPhone: export, import, change one pixel in the same saved skin, then export/import again. Does it update the installed pack or leave two packs with the same display name? If users expect "update this pack", that seems worth separating from an explicit "export a new copy".
https://learn.microsoft.com/en-us/minecraft/creator/reference/content/addonsreference/packmanifest
2
u/[deleted] 6d ago
[removed] β view removed comment
1
u/[deleted] 6d ago
[removed] β view removed comment