{"id":15038479,"url":"https://github.com/openalloc/swifttabler","last_synced_at":"2025-04-09T23:40:47.960Z","repository":{"id":40465312,"uuid":"463796236","full_name":"openalloc/SwiftTabler","owner":"openalloc","description":"A multi-platform SwiftUI component for tabular data","archived":false,"fork":false,"pushed_at":"2023-05-01T23:59:17.000Z","size":1481,"stargazers_count":123,"open_issues_count":14,"forks_count":13,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-07-05T05:04:26.771Z","etag":null,"topics":["coredata","swift","swift-coredata","swift-lang","swift-language","swiftui","swiftui-binding","swiftui-components","swiftui-grid","swiftui-list","swiftui-scrollview","swiftui-tables","tables","tableview","tabular-data","tabular-editor"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openalloc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2022-02-26T08:22:55.000Z","updated_at":"2024-06-19T15:07:04.000Z","dependencies_parsed_at":"2024-01-12T02:14:13.275Z","dependency_job_id":null,"html_url":"https://github.com/openalloc/SwiftTabler","commit_stats":{"total_commits":81,"total_committers":2,"mean_commits":40.5,"dds":"0.12345679012345678","last_synced_commit":"b5503059b10f88b6c94a49dff28e44617bbfa035"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openalloc%2FSwiftTabler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openalloc%2FSwiftTabler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openalloc%2FSwiftTabler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openalloc%2FSwiftTabler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openalloc","download_url":"https://codeload.github.com/openalloc/SwiftTabler/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248131466,"owners_count":21052819,"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":["coredata","swift","swift-coredata","swift-lang","swift-language","swiftui","swiftui-binding","swiftui-components","swiftui-grid","swiftui-list","swiftui-scrollview","swiftui-tables","tables","tableview","tabular-data","tabular-editor"],"created_at":"2024-09-24T20:38:38.948Z","updated_at":"2025-04-09T23:40:47.934Z","avatar_url":"https://github.com/openalloc.png","language":"Swift","readme":"# SwiftTabler\n\nA multi-platform SwiftUI component for tabular data.\n\nAvailable as an open source library to be incorporated in SwiftUI apps.\n\n_SwiftTabular_ is part of the [OpenAlloc](https://github.com/openalloc) family of open source Swift software tools.\n\nmacOS | iOS\n:---:|:---:\n![](https://github.com/openalloc/SwiftTabler/blob/main/Images/macOSa.png)  |  ![](https://github.com/openalloc/SwiftTabler/blob/main/Images/iOSa.png)\n\n## Features\n\n* Convenient display of tabular data from `RandomAccessCollection` data sources\n* Presently targeting macOS v11+ and iOS v14+\\*\n* Supporting both value and reference semantics (including Core Data, which uses the latter)\n* Option to support a bound data source, where inline controls can directly mutate your data model\n* Support for single-select, multi-select, or no selection\n* Option to specify a header and/or footer\n* Option to sort by column in header/footer, with indicators and concise syntax\n* Option to specify a row background and/or overlay\n* On macOS, option for hover events, such as to highlight row under the mouse cursor\n* MINIMAL use of View erasure (i.e., use of `AnyView`), which can impact scalability and performance\\*\\*\n* No external dependencies!\n\nThree table types are supported, as determined by the mechanism by which their header and rows are rendered.\n\n### List\n* Based on SwiftUI's `List`\n* Option to support moving of rows through drag and drop\n* Header/Footer are **inside** scrolling region\n\n### Stack\n* Based on `ScrollView`/`LazyVStack`\n* Header/Footer are **outside** scrolling region\n\n### Grid\n* Based on `ScrollView`/`LazyVGrid`\n* Likely the most scalable and efficient, but least flexible\n* Header/Footer are **outside** scrolling region\n\n\\* Other platforms like macCatalyst, iPad on Mac, watchOS, tvOS, etc. are poorly supported, if at all. Please contribute to improve support!\n\n\\*\\* AnyView only used to specify sort images in configuration, which shouldn't impact scalability.\n\n## Tabler Example\n\nThe example below shows the display of tabular data from an array of values using `TablerList`, a simple variant based on `List`.\n\n```swift\nimport SwiftUI\nimport Tabler\n\nstruct Fruit: Identifiable {\n    var id: String\n    var name: String\n    var weight: Double\n    var color: Color\n}\n\nstruct ContentView: View {\n\n    @State private var fruits: [Fruit] = [\n        Fruit(id: \"🍌\", name: \"Banana\", weight: 118, color: .brown),\n        Fruit(id: \"🍓\", name: \"Strawberry\", weight: 12, color: .red),\n        Fruit(id: \"🍊\", name: \"Orange\", weight: 190, color: .orange),\n        Fruit(id: \"🥝\", name: \"Kiwi\", weight: 75, color: .green),\n        Fruit(id: \"🍇\", name: \"Grape\", weight: 7, color: .purple),\n        Fruit(id: \"🫐\", name: \"Blueberry\", weight: 2, color: .blue),\n    ]\n    \n    private var gridItems: [GridItem] = [\n        GridItem(.flexible(minimum: 35, maximum: 40), alignment: .leading),\n        GridItem(.flexible(minimum: 100), alignment: .leading),\n        GridItem(.flexible(minimum: 40, maximum: 80), alignment: .trailing),\n        GridItem(.flexible(minimum: 35, maximum: 50), alignment: .leading),\n    ]\n\n    private typealias Context = TablerContext\u003cFruit\u003e\n\n    private func header(ctx: Binding\u003cContext\u003e) -\u003e some View {\n        LazyVGrid(columns: gridItems) {\n            Text(\"ID\")\n            Text(\"Name\")\n            Text(\"Weight\")\n            Text(\"Color\")\n        }\n    }\n    \n    private func row(fruit: Fruit) -\u003e some View {\n        LazyVGrid(columns: gridItems) {\n            Text(fruit.id)\n            Text(fruit.name).foregroundColor(fruit.color)\n            Text(String(format: \"%.0f g\", fruit.weight))\n            Image(systemName: \"rectangle.fill\").foregroundColor(fruit.color)\n        }\n    }\n\n    var body: some View {\n        TablerList(header: header,\n                   row: row,\n                   results: fruits)\n    }\n}\n```\n\nWhile `LazyVGrid` is used here to wrap the header and row items, you could alternatively wrap them with `HStack` or similar mechanism.\n\n## Tabler Views\n\n_Tabler_ offers twenty-seven (27) variants of table views from which you can choose. They break down along the following lines:\n\n* Table View - the View name\n* Type - each of the three table types differ in how they render:\n  - **List** - based on `List`\n  - **Stack** - based on `ScrollView`/`LazyVStack`\n  - **Grid** - based on `ScrollView`/`LazyVGrid`\n* Select - single-select, multi-select, or no selection\n* Value - if checked, can be used with value types (e.g., struct values)\n* Reference - if checked, can be used with reference types (e.g., class objects, Core Data, etc.)\n* Bound - if checked, can be used with inline controls (`TextField`, etc.) to mutate model\n* Filter - if checked, `config.filter` is supported (see caveat below)\n\nTable View      | Type      | Select | Value | Reference | Bound | Filter\n:---            | :---      | :---   | :---: | :---:     | :---: | :---:   \n`TablerList`    | **List**  |        |  ✓    |  ✓        |       |  ✓     \n`TablerListB`   | **List**  |        |  ✓    |           |  ✓    |  ✓\\*  \n`TablerListC`   | **List**  |        |       |  ✓        |  ✓    |         \n`TablerList1`   | **List**  | Single |  ✓    |  ✓        |       |  ✓     \n`TablerList1B`  | **List**  | Single |  ✓    |           |  ✓    |  ✓\\*   \n`TablerList1C`  | **List**  | Single |       |  ✓        |  ✓    |         \n`TablerListM`   | **List**  | Multi  |  ✓    |  ✓        |       |  ✓     \n`TablerListMB`  | **List**  | Multi  |  ✓    |           |  ✓    |  ✓\\*   \n`TablerListMC`  | **List**  | Multi  |       |  ✓        |  ✓    |         \n`TablerStack`   | **Stack** |        |  ✓    |  ✓        |       |  ✓     \n`TablerStackB`  | **Stack** |        |  ✓    |           |  ✓    |  ✓\\*   \n`TablerStackC`  | **Stack** |        |       |  ✓        |  ✓    |         \n`TablerStack1`  | **Stack** | Single |  ✓    |  ✓        |       |  ✓     \n`TablerStack1B` | **Stack** | Single |  ✓    |           |  ✓    |  ✓\\*   \n`TablerStack1C` | **Stack** | Single |       |  ✓        |  ✓    |         \n`TablerStackM`  | **Stack** | Multi  |  ✓    |  ✓        |       |  ✓     \n`TablerStackMB` | **Stack** | Multi  |  ✓    |           |  ✓    |  ✓\\*   \n`TablerStackMC` | **Stack** | Multi  |       |  ✓        |  ✓    |         \n`TablerGrid`    | **Grid**  |        |  ✓    |  ✓        |       |  ✓     \n`TablerGridB`   | **Grid**  |        |  ✓    |           |  ✓    |              \n`TablerGridC`   | **Grid**  |        |       |  ✓        |  ✓    |                \n`TablerGrid1`   | **Grid**  | Single |  ✓    |  ✓        |       |  ✓     \n`TablerGrid1B`  | **Grid**  | Single |  ✓    |           |  ✓    |              \n`TablerGrid1C`  | **Grid**  | Single |       |  ✓        |  ✓    |                \n`TablerGridM`   | **Grid**  | Multi  |  ✓    |  ✓        |       |  ✓     \n`TablerGridMB`  | **Grid**  | Multi  |  ✓    |           |  ✓    |              \n`TablerGridMC`  | **Grid**  | Multi  |       |  ✓        |  ✓    |                \n\n\\* filtering with bound values likely not scalable as implemented. If you can find a better way to implement, please submit a pull request!\n\n## Header/Footer\n\nOptionally attach a header (or footer) to your table:\n\n```swift\nvar body: some View {\n    TablerList(header: header,\n               footer: footer,\n               row: row,\n               results: fruits)\n}\n\nprivate func header(ctx: Binding\u003cContext\u003e) -\u003e some View {\n    LazyVGrid(columns: gridItems) {\n        Text(\"ID\")\n        Text(\"Name\")\n        Text(\"Weight\")\n        Text(\"Color\")\n    }\n}\n\nprivate func footer(ctx: Binding\u003cContext\u003e) -\u003e some View {\n    LazyVGrid(columns: gridItems) {\n        Text(\"ID\")\n        Text(\"Name\")\n        Text(\"Weight\")\n        Text(\"Color\")\n    }\n}\n```\n\nWhere you don't want a header (or footer), simply omit from the declaration of the table.\n\nFor **List** based variants, the header and footer are *inside* the scrolling region. For **Stack** and **Grid** based variants, they are *outside*. (This may be configurable at some point once any scaling/performance issues are resolved.)\n\n## Column Sorting\n\nColumn sorting is available through the `tablerSort` view function.\n\nThe examples below show how the header items can support sort.\n\n`.columnTitle()` is a convenience function that displays header name along with an indicator showing the current sort state, if any. Alternatively, build your own header and call the `.indicator()` method to get the active indicator image.  \n\nCaret images are used by default for indicators, but are configurable (see Configuration section below).\n\n### Random Access Collection\n\nFrom the _TablerDemo_ app:\n\n```swift\nprivate typealias Context = TablerContext\u003cFruit\u003e\nprivate typealias Sort = TablerSort\u003cFruit\u003e\n\nprivate func header(ctx: Binding\u003cContext\u003e) -\u003e some View {\n    LazyVGrid(columns: gridItems) {\n        Sort.columnTitle(\"ID\", ctx, \\.id)\n            .onTapGesture { tablerSort(ctx, \u0026fruits, \\.id) { $0.id \u003c $1.id } }\n        Sort.columnTitle(\"Name\", ctx, \\.name)\n            .onTapGesture { tablerSort(ctx, \u0026fruits, \\.name) { $0.name \u003c $1.name } }\n        Sort.columnTitle(\"Weight\", ctx, \\.weight)\n            .onTapGesture { tablerSort(ctx, \u0026fruits, \\.weight) { $0.weight \u003c $1.weight } }\n        Text(\"Color\")\n    }\n}\n```\n\n### Core Data\n\nThe sort method used with Core Data differs. From the _TablerCoreDemo_ app:\n\n```swift\nprivate typealias Context = TablerContext\u003cFruit\u003e\nprivate typealias Sort = TablerSort\u003cFruit\u003e\n\nprivate func header(ctx: Binding\u003cContext\u003e) -\u003e some View {\n    LazyVGrid(columns: gridItems, alignment: .leading) {\n        Sort.columnTitle(\"ID\", ctx, \\.id)\n            .onTapGesture { fruits.sortDescriptors = [tablerSort(ctx, \\.id)] }\n        Sort.columnTitle(\"Name\", ctx, \\.name)\n            .onTapGesture { fruits.sortDescriptors = [tablerSort(ctx, \\.name)] }\n        Sort.columnTitle(\"Weight\", ctx, \\.weight)\n            .onTapGesture { fruits.sortDescriptors = [tablerSort(ctx, \\.weight)] }\n    }\n}\n```\n\n### Sorting on a computed column\n\nWhere there is no key path available to store in the sort context, such as for a computed value, create a place holder key path.\n\n```swift\nextension Holding {\n    func getMarketValue(_ priceMap: [String: Double]) -\u003e Double {\n        shareCount * (priceMap[ticker] ?? 0)\n    }\n\n    var marketValuePlaceholder: Double { 0 }\n}\n\nstruct HoldingsTable: View {\n    private typealias Context = TablerContext\u003cHolding\u003e\n    \n    private func header(_ ctx: Binding\u003cContext\u003e) -\u003e some View {\n        LazyVGrid(columns: gridItems) {\n            // ...\n            Sort.columnTitle(\"Market Value\", ctx, \\.marketValuePlaceholder)\n                .onTapGesture {\n                    tablerSort(ctx, \u0026model.holdings, \\.marketValuePlaceholder) { \n                        $0.getMarketValue(priceMap) \u003c $1.getMarketValue(priceMap) \n                    }\n                }\n        }\n    }\n}\n```\n\n## Bound data\n\nmacOS | iOS\n:---:|:---:\n![](https://github.com/openalloc/SwiftTabler/blob/main/Images/macOSb.png)  |  ![](https://github.com/openalloc/SwiftTabler/blob/main/Images/iOSb.png)\n\nWhen used with 'bound' views (e.g., `TablerListB` or `TablerListC`), the data can be modified directly, mutating your data source. From the demo:\n\n```swift\nprivate func brow(fruit: BoundValue) -\u003e some View {\n    LazyVGrid(columns: gridItems) {\n        Text(fruit.wrappedValue.id)\n        TextField(\"Name\", text: fruit.name)\n            .textFieldStyle(.roundedBorder)\n        Text(String(format: \"%.0f g\", fruit.wrappedValue.weight))\n        ColorPicker(\"Color\", selection: fruit.color)\n            .labelsHidden()\n    }\n}\n```\n\nFor value sources, `BoundValue` is a binding:\n\n```swift\ntypealias BoundValue = Binding\u003cFruit\u003e\n```\n\nFor reference sources, including Core Data, `BoundValue` is an object wrapper (aka 'ProjectedValue'):\n\n```swift\ntypealias BoundValue = ObservedObject\u003cFruit\u003e.Wrapper\n```\n\nNote that for Core Data, the user's changes will need to be saved to the Managed Object Context. See the _TablerCoreDemo_ code for an example of how this might be done.\n\n## Row Background\n\nYou have the option to specify a row background, such as to impart information, or as a selection indicator.\n\nRow Background, as the name suggests, sits BEHIND the row.\n\nmacOS | iOS\n:---:|:---:\n![](https://github.com/openalloc/SwiftTabler/blob/main/Images/macOSc.png)  |  ![](https://github.com/openalloc/SwiftTabler/blob/main/Images/iOSc.png)\n\nAn example of using row background to impart information, as shown above:\n\n```swift\nvar body: some View {\n    TablerList(header: header,\n               row: row,\n               rowBackground: rowBackground,\n               results: fruits)\n}\n\nprivate func rowBackground(fruit: Fruit) -\u003e some View {\n    LinearGradient(gradient: .init(colors: [fruit.color, fruit.color.opacity(0.2)]),\n                   startPoint: .top, \n                   endPoint: .bottom)\n}\n```\n\nAn example of a selection indicator using row background, such as for **Stack** based tables which do not have a native selection indicator:\n\n```swift\n@State private var selected: Fruit.ID? = nil\n\nvar body: some View {\n    TablerStack1(header: header,\n                 row: row,\n                 rowBackground: rowBackground,\n                 results: fruits,\n                 selected: $selected)\n}\n\nprivate func rowBackground(fruit: Fruit) -\u003e some View {\n    RoundedRectangle(cornerRadius: 5)\n        .fill(fruit.id == selected ? Color.accentColor : Color.clear)\n}\n```\n\n## Row Overlay\n\nSimilar to a row background, an overlay can be used to impart information, or to use as a selection indicator.\n\nRow overlay, as the name suggests, sits ATOP the row.\n\nAn example of a selection indicator using row overlay:\n\n```swift\n@State private var selected: Fruit.ID? = nil\n\nvar body: some View {\n    TablerStack1(header: header,\n                 row: row,\n                 rowOverlay: rowOverlay,\n                 results: fruits,\n                 selected: $selected)\n}\n\nprivate func rowOverlay(fruit: Fruit) -\u003e some View {\n    RoundedRectangle(cornerRadius: 5)\n        .strokeBorder(fruit.id == selected ? .white : .clear,\n                      lineWidth: 2,\n                      antialiased: true)\n}\n```\n\n## Hover Events\n\nFor macOS only, you can capture hover events, typically to highlight the row under the mouse cursor.\n\n```swift\n@State private var hovered: Fruit.ID? = nil\n\nvar body: some View {\n    TablerList(.init(onHover: hoverAction),\n               header: header,\n               row: row,\n               rowBackground: rowBackground,\n               results: fruits)\n}\n\nprivate func rowBackground(fruit: Fruit) -\u003e some View {\n    RoundedRectangle(cornerRadius: 5)\n        .fill(Color.accentColor.opacity(hovered == fruit.id ? 0.2 : 0.0))\n}\n\nprivate func hoverAction(fruitID: Fruit.ID, isHovered: Bool) {\n    if isHovered { hovered = fruitID } else { hovered = nil }\n}\n```\n\nTo coordinate hover with other backgrounds, such as for selection on **Stack** tables, see the demo apps.\n\n## Moving Rows\n\nRow moving via drag and drop is available for the **List** based variants.\n\nAn example for use with Random Access Collections, as seen in _TablerDemo_:\n\n```swift\nvar body: some View {\n    TablerList(.init(onMove: moveAction),\n               row: row,\n               results: fruits)\n}\n\nprivate func moveAction(from source: IndexSet, to destination: Int) {\n    fruits.move(fromOffsets: source, toOffset: destination)\n}\n```\n\nTODO need Core Data example, if it's possible to do so.\n\n## Configuration\n\n```swift\nvar body: some View {\n    TablerList(.init(onMove: moveAction,\n                     filter: { $0.weight \u003e 10 },\n                     onHover: hoverAction),\n               header: header,\n               row: row,\n               results: fruits)\n}\n```\n\nConfiguration options will vary by table type.\n\nDefaults can vary by platform (macOS, iOS, etc.). See the code for specifics.\n\nSpacing defaults are driven by the goal of achieving uniform appearance among table types, with the *List* type serving as the standard.\n\n### Base Defaults\n\nBase defaults are defined in the `TablerConfig` module.\n\n- `tablePadding: EdgeInsets` - no padding\n- `sortIndicatorForward: AnyView` - \"chevron.up\" image\n- `sortIndicatorReverse: AnyView` - \"chevron.down\" image\n- `sortIndicatorNeutral: AnyView` - \"chevron.up\" image, with opacity of 0\n\n### List\n\nList configuration is optional.\n\n`TablerListConfig\u003cElement\u003e.init` parameters:\n\n- `canMove: CanMove\u003cElement\u003e` - with a default of `{ _ in true }`, allowing any row to move (if `onMove` defined)\n- `canDelete: CanDelete\u003cElement\u003e` - with a default of `{ _ in true }`, allowing any row to be deleted (if `onDelete` defined), currently only via swipe menu on iOS\n- `onMove: OnMove\u003cElement\u003e?` - with a default of `nil`, prohibiting any move\n- `onDelete: OnDelete\u003cElement\u003e?` - with a default of `nil`, prohibiting any delete, currently only via swipe menu on iOS\n- `filter: Filter?` - with a default of `nil`, indicating no filtering\n- `onHover: (Element.ID, Bool) -\u003e Void` - defaults to `{ _,_ in }`\n- `tablePadding: EdgeInsets` - per Base defaults\n- `sortIndicatorForward: AnyView` - per Base defaults\n- `sortIndicatorReverse: AnyView` - per Base defaults\n- `sortIndicatorNeutral: AnyView` - per Base defaults\n\n### Stack\n\nStack configuration is optional.\n\n`TablerStackConfig\u003cElement\u003e.init` parameters:\n\n- `rowPadding: EdgeInsets` - Stack-specific defaults; varies by platform\n- `headerSpacing: CGFloat` - default varies by platform\n- `footerSpacing: CGFloat` - default varies by platform\n- `rowSpacing: CGFloat` - default of 0\n- `filter: Filter?` - with a default of `nil`, indicating no filtering\n- `onHover: (Element.ID, Bool) -\u003e Void` - defaults to `{ _,_ in }`\n- `tablePadding: EdgeInsets` - default varies by platform\n- `sortIndicatorForward: AnyView` - per Base defaults\n- `sortIndicatorReverse: AnyView` - per Base defaults\n- `sortIndicatorNeutral: AnyView` - per Base defaults\n\n### Grid\n\nGrid configuration is required, where you supply a `GridItem` array.\n\n`TablerGridConfig\u003cElement\u003e.init` parameters:\n\n- `gridItems: [GridItem]` - required\n- `alignment: HorizontalAlignment` - `LazyVGrid` alignment, with a default of `.leading`\n- `itemPadding: EdgeInsets` - Grid-specific defaults, varies by platform\n- `headerSpacing: CGFloat` - default varies by platform\n- `footerSpacing: CGFloat` - default varies by platform\n- `rowSpacing: CGFloat` - default of 0\n- `filter: Filter?` - with a default of `nil`, indicating no filtering\n- `onHover: (Element.ID, Bool) -\u003e Void` - defaults to `{ _,_ in }`\n- `tablePadding: EdgeInsets` - default varies by platform\n- `sortIndicatorForward: AnyView` - per Base defaults\n- `sortIndicatorReverse: AnyView` - per Base defaults\n- `sortIndicatorNeutral: AnyView` - per Base defaults\n\n## Horizontal Scrolling\n\nOn compact displays you may wish to scroll the table horizontally. \n\nYou can wrap in your own `ScrollView`, or alternatively import the [SwiftSideways](https://github.com/openalloc/SwiftSideways) package:\n\n```swift\nimport Tabler\nimport Sideways\n\nvar body: some View {\n    TablerList(header: header,\n               row: row,\n               results: fruits)\n        .sideways(minWidth: 400)\n}\n```\n\n## AutoInit Code Generation\n\n**This applies only to those forking and customizing the _Tabler_ code.**\n\nMany additional `init()` functions for each table variant are generated via the code template `Templates/AutoInit.stencil`.\n\nTo regenerate and re-format, run the [Sourcery](https://github.com/krzysztofzablocki/Sourcery) command from the project directory.\n\n```\n$ brew install sourcery\n$ sourcery\n$ brew install swiftformat\n$ swiftformat **/*.swift   \n```\n\nThe generated code will be found in the `Sources/Generated` directory.\n\n## See Also\n\n* [SwiftSideways](https://github.com/openalloc/SwiftSideways) - minimal horizontal scroller wrapper\n\nApps demonstrating _Tabler_:\n\n* [TablerDemo](https://github.com/openalloc/TablerDemo) - basic use of _Tabler_\n* [TablerCoreDemo](https://github.com/openalloc/TablerCoreDemo) - use of _Tabler_ with Core Data sources\n\nThis library is a member of the _OpenAlloc Project_.\n\n* [_OpenAlloc_](https://openalloc.github.io) - product website for all the _OpenAlloc_ apps and libraries\n* [_OpenAlloc Project_](https://github.com/openalloc) - Github site for the development project, including full source code\n\n## License\n\nCopyright 2021, 2022 OpenAlloc LLC\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\n[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n\n## Contributing\n\nContributions are welcome. You are encouraged to submit pull requests to fix bugs, improve documentation, or offer new features. \n\nThe pull request need not be a production-ready feature or fix. It can be a draft of proposed changes, or simply a test to show that expected behavior is buggy. Discussion on the pull request can proceed from there.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenalloc%2Fswifttabler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenalloc%2Fswifttabler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenalloc%2Fswifttabler/lists"}