v1.5.0
PaletteKit v1.5.0 Release Notes
Highlights
- Async-loading SwiftUI + UIKit views (
AsyncPaletteGraphic,AsyncPaletteGraphicView) — pass an image source, the view extracts the palette internally before rendering. Eliminates the@State + .taskboilerplate for the common case. - Public
PaletteCache— process-wide.sharedsingleton with DI-friendly named instances. URL sources cache automatically;.data/.cgImageopt in viacacheKey:. Sign-out / theme reset clear the store. - Two SwiftUI init styles: simple convenience (auto-renders
PaletteGraphicwith placeholder slot) and phase content closure mirroring Apple'sAsyncImage(url:content:)for telemetry / secondary UI composition / custom error rendering.
Library changes
Sources/PaletteKit/Graphic/Async/:AsyncPaletteGraphic(SwiftUI View) — generic overContent: View, two public inits.AsyncPaletteGraphicView(UIKitUIViewsubclass) — pair ofPaletteGraphicView. PublicimageSource,extractionOptions,configuration,swatchStrategy,cache,cacheKey,placeholderView,transition,onSuccess,onFailure,reload(),cancel().AsyncPaletteGraphicLoader— internal@MainActor ObservableObjectshared by both views. Owns oneTaskper resolution, integrates withPaletteCache, drives the 4-state machine.PaletteCache— public NSCache wrapper,countLimit = 100, String-keyed (debuggable, no hash collision), DI viacache:parameter.AsyncPaletteGraphicTransition+View.asyncPaletteGraphicTransition(_:)modifier — env-driven cross-fade with.normal(0.20s) /.slow(0.35s) /.extraSlow(0.50s) /.custom(_:duration:)presets. Cache hits skip the transition (sync resolution → no animation).AsyncPaletteGraphicPhase— public enum (.empty / .loading / .success(palette:swatches:fromCache:) / .failure(any Error)).
SwatchStrategyextracted to its own file (still public viaConfiguration.swatchStrategy).- Folder renames for naming consistency:
Card/→Graphic/,Support/→Logging/. No public API impact from these renames. - All observable
ExtractionOptionsfields are folded into the cache key (caller changingquality,whiteThreshold,fallbackStrategy, etc. invalidates correctly — no stale palettes).
Breaking changes
CardPalettepublic type removed. The resolved center/edge logic is now an internalPaletteGraphicRenderer.resolveAnchorshelper. Most users never touchedCardPalettedirectly —PaletteGraphic/AsyncPaletteGraphicresolve it internally. Demo lab includes a localResolvedColorsstruct as a reference implementation for callers wanting the same 4-color (center/edge/background/accent) resolution pattern.AsyncPaletteGraphicis now generic overContent: View(wasPlaceholder: View). The convenience placeholder init is unchanged ergonomically but its generic shape changed viawhere Content == AnyView. Callers using the default placeholder (Color.clear) or trailing-closure placeholder are unaffected; explicit type annotations likeAsyncPaletteGraphic<MyView>need updating.- Convenience init's
swatchStrategy:top-level parameter dropped (was duplicatingconfiguration.swatchStrategy). Set viaconfiguration: .init(swatchStrategy: .contrast).
Demo app
- Demo continues to expose the Card lab UI for tweaking direction / colorCount / strategy / grain / axis / shape and exporting share assets. Async-loading-from-URL is documented in DocC + README rather than a parallel demo tab — the wrapper API is simple enough (single line) that runtime exploration adds little value beyond the canonical examples.
Docs
- New DocC article
Loading palettes asynchronouslycovering both init styles, caching, transitions, and error handling. Tutorials/Card.mdgains an## Async loadingsection linking to the article.PaletteKit.mdTopics catalog adds### Async loadingsubsection.- README:
### Load asynchronouslyblock next to### Generate a graphic, install snippet bumped to1.5.0, Roadmap updated (v1.5 ✅, v1.6 PaletteMeshGraphic listed, v2.0 unchanged).
Install
.package(url: "https://github.com/2dubu/PaletteKit", from: "1.5.0")Try it on your device
make demo-appOpen the Card lab tab to tune configuration knobs and export share images.
Audit notes
- Cache architecture follows Lottie's lineage (memory-only NSCache, no disk persistence) — palette is a small derived value (~1KB per entry) and the original image source is the canonical store. Disk persistence is a v1.5.x candidate if real demand surfaces.
- Singleton + per-call DI mirrors Kingfisher / SDWebImage / Nuke convention (immutable
.shared+cache:parameter). - SwiftUI phase content closure mirrors Apple's
AsyncImageprecedent rather than imperative callbacks. UIKit twin retainsonSuccess/onFailurecallbacks because UIKit convention favors callbacks. KarrotUIKit/AsyncImageProviding/provided the architectural blueprint for the 2-View-1-Loader pair pattern + transition env value, with Combine swapped for Swift Concurrency.
Roadmap
- v1.6 —
PaletteMeshGraphic: iOS 18+ multi-color mesh primitive (separate type, narrative-decoupled from PaletteGraphic). - v2.0 —
observe()(live video / camera) +PaletteKitInsights(FoundationModels captions, color naming, custom instructions on iOS 26+).
Acknowledgements
Thanks to color-thief (MIT) for the algorithmic lineage and to KarrotUIKit/AsyncImageProviding/ for the proven SwiftUI+UIKit pair pattern that informed v1.5's architecture. v1.5 ships no extraction algorithm changes from v1.4 — the addition is the async wrapper layer + cache, not the core extractor.