Search code examples
iosswiftmacosswiftuiwkwebview

How to make a dynamic LazyVGrid of custom WebViews with SwiftUI (macOS)?


I'm currently developing an app that shows the code for different SwiftUI files in a single column. I'm using multiple WKWebView instances to display each file (for easy hosting + built-in syntax highlighting support). The WebViews are sized to fit the container with padding:

Screenshot: Single column layout

For some of the larger projects – with numerous files – I'm attempting to implement a LazyVGrid that would display the smaller, supporting files side-by-side as such:

Screenshot: Double column layout

The Issue

However, I have been unable to get the grid to resize into a single-column when the window is made smaller (or even into a 3-column if the user is using a super-wide monitor; although, this is much less of a priority). Is a LazyVGrid the best way to go about this, or should I be taking a different approach? I feel like I'm so close.

GridLayout.swift

struct GridLayout: View {
  let data: [String]
  enum dataType { case title; case url }
  let columns = [GridItem(.flexible()), GridItem(.flexible())]
  
  func get(_ forType: dataType, _ string: String) -> String {
    if forType == .title { return string.components(separatedBy: "*")[0] }
    if forType == .url { return string.components(separatedBy: "*")[1] }
    return ""
  }
  
  var body: some View {
    VStack {
      LazyVGrid(columns: columns) {
        ForEach(data, id: \.self) { i in
          CodeView(title: get(.title, i), height: 500, url: get(.url, i))
          .frame(minWidth: 300, idealWidth: 400, maxWidth: .infinity, alignment: .center)
        }
      }
    }
  }
}

Solution

  • There are several ways to do this with a GeometryReader. Here I’m using .flexible() grid items but varying the number of columns based on the overall width of the parent view: if the width is under 800px, do 1 column; if the width is between 800 and 1200px, do 2 columns; otherwise do 3 columns.

    var body: some View {
        GeometryReader { geometry in
            let gridItem = GridItem(.flexible())
            var columnCount: Int = {
                switch geometry.size.width {
                case 0..<800:
                    return 1
                case 800..<1200:
                    return 2
                default:
                    return 3
                }
            }()
            
            LazyVGrid(columns: Array(repeating: gridItem,
                                     count: columnCount)) {
                ForEach(1..<5) { _ in
                    Rectangle()
                        .fill(Color.gray)
                        .frame(height: 500)
                }
            }
        }
    }