{"id":29459941,"url":"https://github.com/saverio-negro/stackboard","last_synced_at":"2025-07-14T02:03:08.207Z","repository":{"id":304540989,"uuid":"1019056700","full_name":"saverio-negro/StackBoard","owner":"saverio-negro","description":"`StackBoard` is a SwiftUI framework inspired by Apple's native `Form` and `Section` components. It provides a similar declarative interface for grouping related UI elements in a stacked format. It embeds run-time semantic role recognition to identify child `Section` views, performs recursive layout flattening, and meta-data preserving type-erasure.","archived":false,"fork":false,"pushed_at":"2025-07-13T18:53:57.000Z","size":132,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-13T20:24:07.101Z","etag":null,"topics":["apple","framework","swift","swiftui"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saverio-negro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-13T16:35:51.000Z","updated_at":"2025-07-13T18:53:59.000Z","dependencies_parsed_at":"2025-07-13T20:34:19.897Z","dependency_job_id":null,"html_url":"https://github.com/saverio-negro/StackBoard","commit_stats":null,"previous_names":["saverio-negro/stackboard"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/saverio-negro/StackBoard","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FStackBoard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FStackBoard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FStackBoard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FStackBoard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saverio-negro","download_url":"https://codeload.github.com/saverio-negro/StackBoard/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FStackBoard/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265233499,"owners_count":23731800,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple","framework","swift","swiftui"],"created_at":"2025-07-14T02:01:05.511Z","updated_at":"2025-07-14T02:03:08.180Z","avatar_url":"https://github.com/saverio-negro.png","language":"Swift","readme":"# 04 – Hierarchical and Declarative Layout Architecture with `StackBoard`\n\n\u003cimg src=\"./Images/StackBoard.png\" width=\"25%\" /\u003e\n\n## Introduction\n\n`StackBoard` is a SwiftUI layout system inspired by Apple’s native `Form` and `Section` components. It provides a similar declarative interface for grouping related UI elements in a structured, vertically stacked format. This makes it well-suited for settings screens, preference panels, or any UI where logical sectioning improves clarity.\n\nThe goal of `StackBoard` is to mirror the design principles of `Form` while introducing additional architectural clarity. It preserves semantic roles at runtime, making it possible to inspect, traverse, and transform view hierarchies dynamically. While Apple’s native implementation abstracts away structural details for simplicity, `StackBoard` makes them accessible by exposing just enough internals to support recursive layout logic, flattening, and compositional inspection.\n\nThough inspired by Apple’s system, this framework is not an attempt to replace it. Rather, it serves as a learning exercise and an architectural foundation for building extensible layout primitives. `StackBoard` remains lightweight, idiomatic, and fully aligned with SwiftUI’s declarative approach—it simply adds tools to think more deeply about structure and composition.\n\n## Core Design Features\n\n- **Semantic Role Recognition**: Differentiates between atomic views (`StackBoardSection`'s nested views) and structural containers (`StackBoardSection` itself) using marker protocols (`StackBoardSectionSource`).\n- **Recursive Layout Flattening**: Flattens nested section structures into unified visual outputs using `filter(blocks:)`.\n- **Metadata-Preserving Type Erasure**: Leverages custom wrappers to retain behavioral metadata via `AnyBlock` type-erasure.\n- **Declarative Syntax via DSL**: Provides a fluent interface using `@resultBuilder` on `StackBoardBuilder` struct for seamless composition.\n\n`StackBoard` serves as a compositional foundation for architecting type-responsive and declarative layout frameworks in SwiftUI.\n\n---\n\n## The Limitation of Swift's Generic Inference for Type Recognition\n\nSwiftUI’s opaque return types (`some View`) are powerful for abstraction, but they come at a cost: loss of concrete type identity. In other words, opaque return types are compiler-managed types, and Swift is purposefully abstracting them from us for type-safety reasons. \n\nNow, in the context of `StackBoard`, this is a significant hindrance when detecting the role of a child view: I needed a mechanism to identify whether a view is a `StackBoardSection` and, if so, recursively extract and traverse its contents. That way, I was able to filter out any child view that's a `StackBoardSection` and be left with any other view. \n\nThe behavior I just described above resembles the native `Form` component's mechanism, which doesn't allow us to lay out contents in a `Section` view that's nested — however many levels deep (hence the need for recursing) — in a first-level `Section` (main section). In such a case, those child views nested within that deeper `Section` view are going to be recursively collected and displayed — in the order of their appearance (depth-first traversal) — into the main `Section` view.\n\nNow, if you were to rely solely on type casting, that wouldn't be a very flexible option, especially if the `StackBoardSection` struct is using generics:\n\n```swift\npublic struct StackBoardSection\u003cHeader: View, Footer: View\u003e: View {\n```\n\nFor example, look at the following code snippet, which is trying to use optional downcast — using the `as?` operator — with a concrete type `StackBoardSection\u003cText, EmptyView\u003e`:\n\n```swift\nif let section = view as? StackBoardSection\u003cText, EmptyView\u003e {\n    // This will fail unless it's exactly that instantiation\n}\n```\n\nThis becomes problematic due to Swift's use of generics. `StackBoardSection` is a generic type parameterized over `Header` and `Footer`, so each instantiation — e.g., `StackBoardSection\u003cText, EmptyView\u003e` vs. `StackBoardSection\u003cText, Text\u003e` — is treated as a unique type by the compiler. As a result, runtime type checking, or downcasting, is quite fragile, and while recursively traversing our tree of type-erased views (`AnyBlock`), we will likely miss some `StackBoardSection` view, in case we defined our `header` and `footer` properties differently upon instantiating the `StackBoardSection` struct.\n\nTherefore, that rigid behavior undermines our goal of supporting deeply nested sections with flexible headers and footers, and that's the reason why I came up with protocol-oriented programming, using a marker protocol.\n\n### Solution: Marker Protocol and Interface Abstraction\n\nTo overcome this, I introduced a **marker protocol**, `StackBoardSectionSource`, that all `StackBoardSection` types conform to, _regardless_ of their generic parameters. Through this abstraction, the framework can:\n\n- Dynamically recognize whether a view acts as a section.\n- Recursively extract the section’s children.\n- Preserve runtime behavior without depending on exact generic instantiations.\n\n```swift\nprotocol StackBoardSectionSource: View {\n    func extractBlocks() -\u003e [AnyBlock]?\n}\n```\n\nThis design reflects Swift’s protocol-oriented programming philosophy. Rather than relying on fragile downcasting, we introduce flexible, runtime-safe introspection using protocol conformance.\n\n\u003e **Summary:** This design unlocks recursive traversal, compositional transparency, and semantic expressiveness in a type-safe and extensible manner.\n\n---\n\n## AnyBlock: Wrapping Views While Preserving Semantic Metadata\n\nTo further facilitate this flexibility, we introduce `AnyBlock`: a type-erased wrapper around any `View`. Unlike `AnyView`, which erases type without context, `AnyBlock` preserves semantic metadata:\n\n```swift\npublic struct AnyBlock: Identifiable, View {\n    public let id: UUID = UUID()\n    fileprivate let isSection: Bool\n    fileprivate let source: (any StackBoardSectionSource)?\n    private let view: AnyView\n\n    public init\u003cT: View\u003e(_ wrapped: T) {\n        if let source = wrapped as? any StackBoardSectionSource {\n            self.source = source\n            self.isSection = true\n        } else {\n            self.source = nil\n            self.isSection = false\n        }\n        self.view = AnyView(wrapped)\n    }\n\n    public var body: some View {\n        view\n    }\n}\n```\n\nThe `AnyBlock` wrapper is going to play a crucial role when we are recursively traversing the tree of type-erased views. It is key to differentiating between leaf views (view nodes with no children) and structural containers (i.e., sections), enabling runtime behavior branching.\n\n---\n\n## DSL with `@resultBuilder`\n\nTo maintain SwiftUI’s expressive declarative syntax, we define a domain-specific language using `@resultBuilder`. This DSL automatically wraps each view in an `AnyBlock`, producing a uniform, inspectable data structure:\n\n```swift\n@resultBuilder\npublic struct StackBoardBuilder {\n    public static func buildBlock(_ components: AnyBlock...) -\u003e [AnyBlock] {\n        components\n    }\n\n    @MainActor\n    public static func buildExpression\u003cT: View\u003e(_ expression: T) -\u003e AnyBlock {\n        return AnyBlock(expression)\n    }\n}\n```\n\nThe `AnyBlock` wrapper takes on an essential role in embedding all views under a _unified_ type (`AnyBlock`) by type-erasing them — that's what our `buildExpression` function is doing. At compile time, Swift runs `buildExpression` for each `View` object within the body of the function being wrapped with `@resultBuilder`. That makes it possible for `buildBlock` to manage a list of homogenous, type-erased views (`AnyBlock`) and avoid conflicting types, had we accepted mixed `View` objects via a generic type parameter `T: View` and let the Swift compiler infer the type according to the first element in the variadic list, whose type may not to match the type of the remaining views within the `components` variadic parameter.\n\nHowever, despite `AnyBlock` being a type-eraser, it differs from `AnyView`. That is, before wrapping our concrete type within `AnyView`, it preserves important metadata — stored in `isSection` and `source` — as a means to perform section role recognition. \n\n\u003e **Summary**: Using type-erased views guarantees compatibility with heterogeneous views while maintaining structural semantics.\n\n---\n\n## StackBoardSection: Compositional Grouping\n\n`StackBoardSection` acts as a logical grouping mechanism with optional headers and footers. It conforms to `StackBoardSectionSource` and exposes/extracts its child views through `extractBlocks()`:\n\n```swift\nextension StackBoardSection: StackBoardSectionSource {\n    func extractBlocks() -\u003e [AnyBlock]? {\n        self.blocks\n    }\n}\n```\n\n```swift\npublic struct StackBoardSection\u003cHeader: View, Footer: View\u003e: View {\n    public let blocks: [AnyBlock]\n    public let header: Header\n    public let footer: Footer\n\n    public init(\n        @StackBoardBuilder content: () -\u003e [AnyBlock],\n        @ViewBuilder header: () -\u003e Header,\n        @ViewBuilder footer: () -\u003e Footer\n    ) {\n        self.blocks = content()\n        self.header = header()\n        self.footer = footer()\n    }\n\n    public var body: some View {\n        VStack {\n            HStack {\n                header\n                    .padding(.leading, 15)\n                    .padding(.top, 2)\n                    .foregroundStyle(.gray)\n                Spacer()\n            }\n\n            StackBoardSectionContent(blocks: blocks)\n            footer\n        }\n        .frame(maxWidth: .infinity)\n        .padding(.horizontal, 20)\n        .padding(.vertical, 35)\n    }\n}\n```\n\n---\n\n## Recursive Tree Traversal\n\nTo support arbitrarily nested sections, `StackBoardSectionContent` recursively \"flattens\" its input. In other words, it extracts all child views in any `Section` view that is any level deeper than the main one — `StackBoard` local scope. It achieves it by performing a depth-first traversal of `AnyBlock` wrappers using the `filter(blocks:)` recursive function.\n\n```swift\nprivate func filter(blocks: [AnyBlock]) -\u003e [AnyBlock] {\n    var flat = [AnyBlock]()\n\n    for block in blocks {\n        if let nested = block.source?.extractBlocks() {\n            flat.append(contentsOf: filter(blocks: nested))\n        } else {\n            flat.append(block)\n        }\n    }\n\n    return flat\n}\n```\n\nThis \"flattening\" ensures visual consistency and avoids the rendering of `Section` views — sections are rendered differently than any other views — within other `Section` views, limiting the rendering of `Section` to the first layer — in `StackBoard`.\n\n---\n\n## StackBoard: Rendering Logic\n\nThe `StackBoard` view uses metadata in `AnyBlock` to decide rendering strategy at runtime:\n\n```swift\npublic struct StackBoard: View {\n    let blocks: [AnyBlock]\n\n    public init(@StackBoardBuilder content: () -\u003e [AnyBlock]) {\n        self.blocks = content()\n    }\n\n    public var body: some View {\n        VStack {\n            VStack(spacing: 1) {\n                ForEach(blocks) { block in\n                    // Based on preserved metadata, before type-erasure, it either renders the view as a section or as any other view.\n                    if block.isSection {\n                        block\n                    } else {\n                        VStack {\n                            block.padding(10)\n                        }\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                        .background(\n                            RoundedRectangle(cornerRadius: 10)\n                                .fill(.white)\n                                .shadow(color: .black.opacity(0.05), radius: 1, x: 0, y: 2)\n                        )\n                        .padding(.horizontal, 20)\n                    }\n                }\n            }\n            .padding(.top, 20)\n            Spacer()\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .background(Color.gray.opacity(0.05))\n    }\n}\n```\n\n### Example: `StackBoard` API Usage\n\n```swift\nStackBoard {\n            \n    // Section behavior parsed\n    StackBoardSection(\"Account\") {\n        Text(\"Name\")\n        Text(\"Email\")\n    }\n    \n    // Section behavior parsed\n    StackBoardSection(\"Preferences\") {\n        \n        // Section behavior ignored\n        StackBoardSection(\"Notifications (Ignored)\") {\n            Text(\"Push\")\n            Text(\"Email\")\n            \n            // Section behavior ignored\n            StackBoardSection(\"Modes (Ignored)\") {\n                Text(\"Save Power\")\n                Text(\"Sleep Mode\")\n            }\n        }\n        \n        Text(\"Dark Mode\")\n    }\n    \n    // Section behavior parsed\n    StackBoardSection(\"Footer\") {\n        Text(\"Footer Info\")\n    }\n}\n```\n\nThis produces a flat, semantically structured layout regardless of nested depth.\n\n---\n\n## Wrap-up: Main Features of `StackBoard`\n\n- **Reflective Composition**: Enables runtime hierarchy evaluation.\n- **Protocol-Driven Design**: Avoids fragile generic specializations via protocol conformance.\n- **Structural Flattening**: Supports deeply nested yet visually linear layouts via recursive extraction of atomic views.\n\n---\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaverio-negro%2Fstackboard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaverio-negro%2Fstackboard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaverio-negro%2Fstackboard/lists"}