13

I'm trying to implement a view that contains a LazyVGrid of staggered images in a grid- as seen in the below Pinterest feed:

enter image description here

I am aware of the WaterfallGird library but I was wondering if there would be a way to implement this functionality with a LazyGrid, instead of an ordinary V/HGrid.

mstv2020
  • 143
  • 2
  • 6

2 Answers2

5

Beau Nouvelle solution is working, however, it's buggy and glitchy at least of me. Instead of using LazyVGrid if we use HStack with alignment: .top It works better.

Here is the view

var body: some View {

        HStack(alignment: .top) {            
            LazyVStack(spacing: 8) {
                ForEach(splitArray[0]) {...}
            }
            
            LazyVStack(spacing: 8) {
                ForEach(splitArray[1]) {...}
            }
        }
    }

Here is the code to split the array

    private var splitArray: [[Photo]] {
        var result: [[Photo]] = []
        
        var list1: [Photo] = []
        var list2: [Photo] = []
        
        photos.forEach { photo in
            let index = photos.firstIndex {$0.id == photo.id }
            
            if let index = index {
                if index % 2 == 0  {
                    list1.append(photo)
                } else {
                    list2.append(photo)
                }
            }
        }
        result.append(list1)
        result.append(list2)
        return result
        
    }

I know this is not performance but so far only working solution I found.

Here is the full source code

enter image description here

Watery Desert
  • 364
  • 1
  • 7
  • 17
  • I'm trying to figure out how to do this with any number of columns depending on the space available (like LazyGrid does, by accepting a minimum width for the columns). After a couple failed attempts I'm realizing it's harder than I thought it would be. If anyone has done this or has some ideas how to approach it, I'd appreciate it very much – Subcreation Aug 14 '22 at 22:53
3
  1. Split your array into the number of columns you need.
  2. Inside your LazyVGrid create 2 VStacks.
  3. Drop a ForEach in both VStacks and use each of your arrays you created earlier to fill them out.
  4. That’s it

A rough example as follows...

// split array
let splitArrays = ([TileItem], [TileItem])

ScrollView {
    LazyVGrid(*setup GridItems here*) {
        VStack {
            ForEach(splitArrays.0) { tile in
                Image(tile.image)
            }
        }
        VStack {
            ForEach(splitArrays.1) { tile in
                Image(tile.image)
            }
        }
    }
}

It's likely that this isn't very performant, but it should be able to do what you're after.

Beau Nouvelle
  • 6,962
  • 3
  • 39
  • 54
  • When I read this awhile ago, I though this was a wrong approach. After hours of researching and trying things out I finally conclude that this probably is the simplest and best approach to take. I upvoted your answer so your downvote would cancel out :) – Archie G. Quiñones Jun 30 '21 at 16:13
  • Although I would suggest using a `LazyVStack` instead of a `VStack`. – Archie G. Quiñones Jun 30 '21 at 16:14
  • The only problem with this approach is that it works as long as the Staggred effect is only in one direction (either Vertical or Horizontal). When dealing staggered in both direction, it's really up to you to solve layout the items. – Archie G. Quiñones Jun 30 '21 at 16:17
  • Yeah, another issue is retaining the item sort order. Even after de-interlacing your array into two, if elements on one side are much smaller, you won't have to scroll far before the sorting gets weird. The only other option is to use a wrapped collection view but I figured thats not what the question was asking. – Beau Nouvelle Jul 04 '21 at 02:05