Home > Net >  How to make SwiftUI Grid lay out evenly based on width?
How to make SwiftUI Grid lay out evenly based on width?

Time:05-19

I'm trying to use a SwiftUI Lazy Grid to lay out views with strings of varying lengths. How can I construct my code so that, e.g. if 3 view's do not fit, it will only make 2 columns and push the 3rd view to the next row so that they won't overlap?

struct ContentView: View {
    var data = [
    "Beatles",
    "Pearl Jam",
    "REM",
    "Guns n Roses",
    "Red Hot Chili Peppers",
    "No Doubt",
    "Nirvana",
    "Tom Petty and the Heart Breakers",
    "The Eagles"
   
    ]
    
    var columns: [GridItem] = [
        GridItem(.flexible()),
        GridItem(.flexible()),
        GridItem(.flexible())
    ]
    
    
    var body: some View {
        LazyVGrid(columns: columns, alignment: .center) {
            ForEach(data, id: \.self) { bandName in
                Text(bandName)
                    .fixedSize(horizontal: true, vertical: false)
            }
        }
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

enter image description here

CodePudding user response:

Preview

You can use this method to achieve what you're looking for, solution source: https://www.fivestars.blog/articles/flexible-swiftui/

ContentView

struct ContentView: View {
// MARK: - PROPERTIES

var data = [
    "Beatles",
    "Pearl Jam",
    "REM",
    "Guns n Roses",
    "Red Hot Chili Peppers",
    "No Doubt",
    "Nirvana",
    "Tom Petty and the Heart Breakers",
    "The Eagles"
   
    ]

// MARK: - BODY

var body: some View {
    FlexibleView(
        availableWidth: UIScreen.main.bounds.width, data: data,
        spacing: 15,
        alignment: .leading
      ) { item in
        Text(verbatim: item)
          .padding(8)
          .background(
            RoundedRectangle(cornerRadius: 8)
              .fill(Color.gray.opacity(0.2))
           )
      }
      .padding(.horizontal, 10)
    }

}

// MARK: - PREVIEW

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

FlexibleView

// MARK: - FLEXIBLE VIEW

struct FlexibleView<Data: Collection, Content: View>: View where Data.Element: Hashable {
let availableWidth: CGFloat
let data: Data
let spacing: CGFloat
let alignment: HorizontalAlignment
let content: (Data.Element) -> Content
@State var elementsSize: [Data.Element: CGSize] = [:]

var body : some View {
    VStack(alignment: alignment, spacing: spacing) {
        ForEach(computeRows(), id: \.self) { rowElements in
            HStack(spacing: spacing) {
                ForEach(rowElements, id: \.self) { element in
                content(element)
                        .fixedSize()
                        .readSize { size in
                            elementsSize[element] = size
                        }
                }
            }
        }
    }
}

func computeRows() -> [[Data.Element]] {
    var rows: [[Data.Element]] = [[]]
    var currentRow = 0
    var remainingWidth = availableWidth

    for element in data {
      let elementSize = elementsSize[element, default: CGSize(width: availableWidth, height: 1)]

      if remainingWidth - (elementSize.width   spacing) >= 0 {
        rows[currentRow].append(element)
      } else {
        currentRow = currentRow   1
        rows.append([element])
        remainingWidth = availableWidth
      }

      remainingWidth = remainingWidth - (elementSize.width   spacing)
    }

    return rows
}
}

View Extension

// MARK: - EXTENSION

extension View {
    func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
        background(
          GeometryReader { geometryProxy in
            Color.clear
              .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
          }
        )
        .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
    }
}

private struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
  • Related