Manel
Manel

Reputation: 1654

How to create "Masonry like" layout with SwiftUI2 grid?

Is SwiftUI 2 grid system is able to render "Masonry like" layouts ?

A "Masonry" layout will look like this :

Masonry image layout

The remarkable feature of this layout is that content can span on multiple columns.

This does not seems automatically possible with LazyVGrid or LazyHGrid as they rely on GridItem which seems to describe a column (either fixed, flexible or adaptive).

If you think in term of column this design cannot be achieved.

Did i missed something or is it true we cannot make this kind of grid ?

Upvotes: 2

Views: 2100

Answers (1)

cbjeukendrup
cbjeukendrup

Reputation: 3446

I must admit that I haven't done much research for Lazy Grids yet, but as I saw some examples, I'm afraid that it will not be possible that way. But my programming mindset is: anything is possible. So let's make our own solution! Here's mine:

struct ContentView: View {
    let items = (1 ... 12).map { "Item \($0)" }
    var range: Range<Int> { 0 ..< Int((Double(items.count) / 3).rounded(.up)) }
    var body: some View {
        GeometryReader { geometry in
            ScrollView {
                LazyVStack { // still some laziness 
                    ForEach(range, id: \.self) { index in
                        HStack(spacing: 0) {
                            if index % 2 == 0 {
                                Text(items[index * 3])
                                    .frame(maxHeight: .infinity)
                                    .frame(width: geometry.size.width * 2/3)
                                    .background(Color(#colorLiteral(red: 0.3427395821, green: 0.7238617539, blue: 0.6179549098, alpha: 1)))
                                VStack(spacing: 0) {
                                    Text(items[index * 3 + 1])
                                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                                        .background(Color(#colorLiteral(red: 0.1351453364, green: 0.1446713805, blue: 0.2671209574, alpha: 1)))
                                    Text(items[index * 3 + 2])
                                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                                        .background(Color(#colorLiteral(red: 0.9248386621, green: 0.3957888484, blue: 0.3508865833, alpha: 1)))
                                }
                                .frame(maxHeight: .infinity)
                                .frame(width: geometry.size.width * 1/3)
                            } else {
                                VStack(spacing: 0) {
                                    Text(items[index * 3])
                                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                                        .background(Color(#colorLiteral(red: 0.1351453364, green: 0.1446713805, blue: 0.2671209574, alpha: 1)))
                                    Text(items[index * 3 + 1])
                                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                                        .background(Color(#colorLiteral(red: 0.9248386621, green: 0.3957888484, blue: 0.3508865833, alpha: 1)))
                                }
                                .frame(maxHeight: .infinity)
                                .frame(width: geometry.size.width * 1/3)
                                Text(items[index * 3 + 2])
                                    .frame(maxHeight: .infinity)
                                    .frame(width: geometry.size.width * 2/3)
                                    .background(Color(#colorLiteral(red: 0.226172477, green: 0.3690122366, blue: 0.3273729682, alpha: 1)))
                            }
                        }
                        .frame(height: geometry.size.width * 6/16)
                    }
                }
            }
        }
        .foregroundColor(.white)
    }
}

Explanation:

  • The line with let items = ... is just a quick way to generate a list with strings "Item 1" through "Item 12".
  • Let's consider each group of three items as a "row". Then the variable range will contain the number of rows.
  • For each row, we create an HStack. Inside it, there will be one big item and two smaller ones in a VStack. In the even rows (index % 2 == 0), the VStack will be on the right, in the odd rows, it will be on the left.
  • We use .frame(maxHeight: .infinity) to make the items fill the available space.
  • But how do we get the right item at the right place? Well, as you can see, I worked with index * 3, index * 3 + 1 and index * 3 + 2, where index is the row number. I will illustrate that with the table below:
    +-------------------------------------------+-------------------------------------------+-------------------------------------------+-------------------------------------------+
    | index = 0                                 | index = 1                                 | index = 2                                 | index = 3                                 |
    +-----------+---------------+---------------+-----------+---------------+---------------+-----------+---------------+---------------+-----------+---------------+---------------+
    | index * 3 | index * 3 + 1 | index * 3 + 2 | index * 3 | index * 3 + 1 | index * 3 + 2 | index * 3 | index * 3 + 1 | index * 3 + 2 | index * 3 | index * 3 + 1 | index * 3 + 2 |
    |    = 0    |      = 1      |      = 2      |    = 3    |      = 4      |      = 5      |    = 6    |      = 7      |      = 8      |    = 9    |      = 10     |      = 11     |
    +-----------+---------------+---------------+-----------+---------------+---------------+-----------+---------------+---------------+-----------+---------------+---------------+
    

Upvotes: 1

Related Questions