Skip to content

Add state management templates#64

Open
ssestak wants to merge 6 commits into
mainfrom
feature/state-management-templates
Open

Add state management templates#64
ssestak wants to merge 6 commits into
mainfrom
feature/state-management-templates

Conversation

@ssestak

@ssestak ssestak commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends the SwiftUI App Xcode template with a reusable state-management layer (loading / empty / error / ready) and adds a new Projection Scene variant to the Scene template. Existing example files are updated to demonstrate the cache-projection pattern end-to-end. Also adds a build-time warning to remind developers to set UILaunchScreen in Info.plist.

Architecture

flowchart LR
    subgraph Model["Model layer (new)"]
        CS[ComponentState]
        IS[ItemState]
        AS[ArrayState]
        SIC[StateInfoConfig]
        CP[CacheProjection]
        MK[Mockable]
    end

    subgraph Views["UI layer (new)"]
        SIV[StateInfoView]
        CSV[ComponentStateView]
        ISV[ItemStateView]
    end

    subgraph Scene["Example scene (updated)"]
        ECM[ExampleComponentModel]
        ECP[ExampleCacheProjection]
        EC[ExampleComponent]
    end

    DC[(DataCache<DataCacheModel>)] --> ECM
    ECM -->|projection| ECP
    ECP -->|state| CS
    EC -->|wraps content| CSV
    CSV --> CS
    CSV -.uses.-> SIV
    ISV --> IS
    SIV --> SIC

    CS -.config.-> SIC
    IS -.config.-> SIC
    AS -.config.-> SIC
    ECP -.conforms.-> CP
Loading

Key Changes

  • SwiftUI App template — adds ComponentState, ItemState, ArrayState enums for screen / item / collection lifecycle, plus StateInfoConfig and a unified StateInfoView for empty/error presentation. ComponentStateView and ItemStateView wrap content and switch on state.
  • CacheProjection pattern — generic CacheProjection protocol with concrete ExampleCacheProjection; the example ComponentModel exposes a computed projection property that reads dataCache.value, so @Observable re-renders the view on cache changes (no Combine).
  • Scene template — new Projection Scene variant generates Component, ComponentModel, and CacheProjection together; adds a projectionName template option.
  • Swift 6 concurrency — state and data model types marked nonisolated so they can be used from any actor context.
  • App template warning — emits a #warning to add UILaunchScreen to Info.plist, preventing the legacy compatibility-mode pitfall on modern devices.

Test Plan

  • Generate a new project from the SwiftUI App template and verify it builds
  • Create a Projection Scene via the new template variant and verify file layout / naming
  • Confirm ComponentStateView renders loading / empty / error / ready states correctly in previews

Šimon Šesták added 5 commits May 12, 2026 13:10
Add CacheProjection, ComponentState, ItemState, ArrayState and their
corresponding SwiftUI views to the SwiftUI App template. Update the
Example scene to demonstrate the full CacheProjection data flow with
@observable computed properties.

Add "Projection Scene" variant to the Scene template that scaffolds
a Component, ComponentModel and CacheProjection stub.
Merge ErrorViewConfig and the missing EmptyViewConfig into a single
StateInfoConfig carried inside ComponentState's empty/error cases. The
config requires a config wherever the state is non-populated, which
prevents the EmptyView() placeholder anti-pattern observed in production.

StateInfoView is the baseline renderer for empty and error; a TODO asks
each project to swap it for its design system.

ComponentStateView keeps four generics but adds seven extension inits so
callers can default empty/error to StateInfoView and only customize what
they need. ItemStateView gains equivalent convenience inits.

Drop ArrayStateView — production evidence (jt-assets) shows ArrayState
is always converted to ItemState via a computed property on the Data
struct, so a dedicated array view is dead weight.

Use @ProxyMembers from futured-macros instead of the hand-rolled
subscript(dynamicMember:) on cache projections. CacheModel is now
inferred from the data(from:) signature; the default nil data(for:from:)
is constrained to where ID == Void so projections with a real ID are
forced to implement it.

Extract the Example component's content into a private computed view
and clarify the observation comment on the computed projection.
In Swift 6 default MainActor isolation, value types default to MainActor
unless annotated otherwise. DataCacheModel must stay nonisolated (it
crosses the DataCache boundary), so every type it transitively equates
against also needs to be nonisolated — otherwise the synthesized
Equatable conformance fails with "Main actor-isolated conformance
cannot be used in nonisolated context".

ExampleItem moves out of the extension so it sits alongside
DataCacheModel at file scope with explicit nonisolated.

Restore @dynamicMemberLookup on cache projections so @ProxyMembers
can generate its subscript.
Without UILaunchScreen (or UILaunchStoryboardName) iOS runs the app in
legacy compatibility mode and the UI is letterboxed instead of filling
the screen on modern devices. A compile-time #warning in App.swift
surfaces the requirement in the build log.
ArrayState was an unused state wrapper — render arrays via the
CacheProjection -> ItemState -> ItemStateView pipeline instead, which
keeps filtering and sorting out of views.
@ssestak ssestak requested a review from jmarek41 May 12, 2026 13:09

@jmarek41 jmarek41 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Krása 👏. Jen pokud bychom přidali i ten SwiftFormat tak to chce čeknout že nebude nic měnit v těchto filech po prvním spuštění ať je to čistota. Myslím v těch vygenerovaných filech.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants