Skip to content

Conversation

@JPToroDev
Copy link
Collaborator

This PR completes the expressive typing work begun in 0.6.0 by introducing variadic generics (SwiftUI's approach) to the framework.

While this primarily affects internal types, several surface API changes were unavoidable:

Unavoidable API Changes

Grid API Redesign

The biggest change is to the Grid API, whose previous implementation had become unmaintainable.

The new API is modeled after SwiftUI, uses CSS Grid instead of Bootstrap, and leverages deprecation messages for non-breaking changes.

Before:

Grid {
    Text("1")
        .width(1)
    Text("2")
        .width(1)
}
.columns(2)

After:

Grid(columns: 2) {
    Text("1")
        .gridCellColumns(1)
    Text("2")
        .gridCellColumns(1)
}

However, Grid without modifiers now behaves differently:

Grid {
    Text("1")
    Text("2")
}

Like SwiftUI, each Text now takes up its own full row.

For a complete overview of the new Grid API, visit: https://github.com/JPToroDev/IgniteSamples/blob/current/Sources/Pages/Concepts/GridExamples.swift

tagLinks() Return Type Change

Since Link is now generic, Article/tagLinks() returns some HTML instead of [Link]:

Before:

let tagLinks = article.tagLinks()
if let tagLinks {
    Section {
        ForEach(tagLinks) { link in
            link
        }
    }
}

After:

Section {
    article.tagLinks()
}

To simplify manual Link construction, Article/links now returns [TagMetadata] instead of [String]. TagMetadata contains both the tag name and URL.

font() Modifier for Text

To change the semantic HTML tag of Text elements (<p>, <h1>, etc.), ensure .font() is the first modifier applied. Otherwise, it will only affect font size without changing the underlying HTML tag. Introducing environment modifiers will remove this restriction in the future.

Protocol and Type Naming Changes

  1. Protocol names changed following the pattern: NavigationItemNavigationElement. These shouldn't be directly conformed to, so impact should be minimal.

  2. Nested types were flattened: Card.StyleCardStyle. This should affect few users.

On Variadic Generics

Previously, Ignite erased almost everything to AnyHTML, losing important type information. This made it difficult to handle type-specific behaviors in complex view hierarchies.

Variadic generics preserve type information by allowing wrapper types to conditionally inherit protocol conformances from their content. For example, when a Link (which conforms to LinkProvider) is wrapped in modifiers, the resulting ModifiedHTML type also conforms to LinkProvider.

This enables better type safety and more sophisticated compile-time optimizations while maintaining SwiftUI-like ergonomics. The upgrade required several new internal types: HTMLModifier, PackHTML (equivalent to SwiftUI's TupleView), and new result builders.

@JPToroDev JPToroDev changed the title Introduce Variadic Generic for Expressive Typing Ignite v0.6.5: Introduce Variadic Generic for Expressive Typing Jun 25, 2025
@ralfboernemeier
Copy link

@ralfboernemeier If you'd be willing: could you give the new type-preservation branch a try and let me know if you encounter any issues using the update guide above?

I would like to try it, but I currently have very little free time in my spare time. I’ll try it as soon as I have time, but unfortunately I can’t promise anything - sorry.

@JPToroDev
Copy link
Collaborator Author

@ralfboernemeier If you'd be willing: could you give the new type-preservation branch a try and let me know if you encounter any issues using the update guide above?

I would like to try it, but I currently have very little free time in my spare time. I’ll try it as soon as I have time, but unfortunately I can’t promise anything - sorry.

No pressure. Keeping you in the loop to make sure we address your concerns!

@ralfboernemeier
Copy link

ralfboernemeier commented Jul 6, 2025

@JPToroDev
I have tested the branch in my sources and get an error with Table:

Table {
            for (datum, medikament) in dataWurmkur {
                Row {
                    datum
                    medikament
                }
            }
        } header: {
            "Datum"
            "Medikament"
        }
        .tableStyle(.stripedRows)
        .margin(.bottom, .xLarge)

Errors shown:

Closure containing control flow statement cannot be used with result builder 'TableElementBuilder'
Cannot convert return expression of type 'some HTML' to return type 'EmptyHTML'

I admit that I probably don't have enough know-how to derive the solution from the above explanations. For me it would be helpful if, like the example with Grid, which I find very helpful, an example of the changes in Table were listed.
Can you help me/explain what I need to change here?

My code works with Ignite 0.6.0 without any problems.

@MrSkwiggs
Copy link
Collaborator

Table {
            for (datum, medikament) in dataWurmkur {
                Row {
                    datum
                    medikament
                }
            }
        } header: {
            "Datum"
            "Medikament"
        }
        .tableStyle(.stripedRows)
        .margin(.bottom, .xLarge)

Errors shown:

Closure containing control flow statement cannot be used with result builder 'TableElementBuilder'
Cannot convert return expression of type 'some HTML' to return type 'EmptyHTML'

I admit that I probably don't have enough know-how to derive the solution from the above explanations. For me it would be helpful if, like the example with Grid, which I find very helpful, an example of the changes in Table were listed. Can you help me/explain what I need to change here?

My code works with Ignite 0.6.0 without any problems.

@ralfboernemeier Most likely due to the use of a regular for ... in loop. Try this instead:

Table {
  ForEach(dataWurmkur) { datum, medikament in
    Row {
      datum
      medikament
    }
  }
}

@ralfboernemeier
Copy link

@MrSkwiggs with the suggested code I got the following error message in Xcode:

Struct 'TableElementBuilder' requires that 'some HTML' conform to 'TableElement'

@ralfboernemeier
Copy link

ralfboernemeier commented Jul 8, 2025

@MrSkwiggs I think I have found the solution, your suggestion was very helpful - thank you! With the following code there is no more error message and the table is displayed correctly at first glance:

Table(dataWurmkur) { datum, medikament in
            Row {
                datum
                medikament
            }
        } header: {
            "Datum"
            "Medikament"
        }
        .tableStyle(.stripedRows)
        .margin(.bottom, .xLarge)

@ralfboernemeier
Copy link

@JPToroDev After I was able to solve the Table() problem, it was with the help of the explanations here in the ticket, in my case in particular the changes to Grid, very easy to change my website in the branch and compile it successfully. I find the idea with the examples - before and after - very helpful and intuitive!

@ralfboernemeier
Copy link

ralfboernemeier commented Jul 9, 2025

@JPToroDev May I ask a question about what I consider to be a breaking change? I have the following code:

Grid(columns: 4) {
            Text {
                Link(Image("/images/1.svg", description: "Claude").resizable().frame(width: 80, height: 80), target: "https://claude.ai")
                    .target(.blank)
                    .role(.secondary)
            }
            .gridCellColumns(1)
            .horizontalAlignment(.center)
            
            Text {
                Link(Image("/images/2.svg", description: "Speed").resizable().frame(width: 80, height: 80), target: Speedtest())
                    .target(.parent)
                    .role(.secondary)
            }
            .gridCellColumns(1)
            .horizontalAlignment(.center)
            
            Text {
                Link(Image("/images/3.svg", description: "Fritz!").resizable().frame(width: 80, height: 80), target: "http://target2.html")
                    .target(.blank)
                    .role(.secondary)
            }
            .gridCellColumns(1)
            .horizontalAlignment(.center)
            
            Text {
                Link(Image("/images/4.svg", description: "Cloud").resizable().frame(width: 80, height: 80), target: "https://target4.html")
                    .target(.blank)
                    .role(.secondary)
            }
            .gridCellColumns(1)
            .horizontalAlignment(.center)
            
        }
        .padding(.top, 20)

With 0.6.0, the 4 images are displayed in a row on my mobile device, i.e. next to each other. With the branch, the images on my mobile device are displayed one below the other, i.e. 4 rows below each other. It does not matter whether I reduce the size of the images to ‘20’, for example. On a PC, the icons in both versions are displayed next to each other in one row, as desired.
Is there an explanation for this, or even better, a solution?

@JPToroDev JPToroDev changed the title Ignite v0.6.5: Introduce Variadic Generic for Expressive Typing Ignite v0.6.5: Introduce Variadic Generics for Expressive Typing Jul 9, 2025
@JPToroDev
Copy link
Collaborator Author

JPToroDev commented Jul 9, 2025

Adding gridItemSizing(.adaptive(minimum: 0)) to the grid will ensure its items remain in the same row on mobile.

@JPToroDev
Copy link
Collaborator Author

Thank you for giving this branch a whirl and for offering all the great feedback!

@ralfboernemeier
Copy link

@JPToroDev Thank you very much - that answered my question and solved the problem!

@JPToroDev
Copy link
Collaborator Author

@twostraws Hope you've been doing well through the iOS and macOS 26 release cycle—I know how hectic they are. When you’re over the finish line with it, would you mind taking a look at this PR? I kept all the commits nice and atomic to make the review easier.

@JPToroDev JPToroDev closed this Oct 22, 2025
@JPToroDev JPToroDev deleted the type-preservation branch November 6, 2025 18:22
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.

4 participants