admin管理员组

文章数量:1122846

I have a LazyVGrid inside a ScrollView with a single adaptive column.

import SwiftUI

struct MyView: View {
    var numbers = 1...30
    
    var body: some View {
        let columns = [
            GridItem(.adaptive(minimum: 50), spacing: 5)
        ]
        
        VStack {
            GeometryReader { geometry in
                ScrollView {
                    LazyVGrid(columns: columns, spacing: 5) {
                        ForEach(numbers, id: \.self) { i in
                            Text(String(i))
                                .frame(width: 50, height: 50)
                                .border(.blue)
                        }
                    }
                    .border(.red)
                    .padding()
                    .frame(minWidth: geometry.size.width, minHeight: geometry.size.height)
                }
            }
        }.frame(width: 500, height: 200)
    }
}

import PlaygroundSupport
PlaygroundPage.current.setLiveView(MyView())

This looks fine when there are a lot of items in the grid:

But when there are few items, I would prefer the items to be centered. Instead, they are left-aligned in a single row. The LazyVGrid appears to take up all the horizontal space, even if it doesn't need it.

How can I achieve horizontal centering, on iOS 14?

NB: The ScrollView may not have anything to do with the problem at hand. But I mention it because I had to use GeometryReader in order to vertically center the contents when using a ScrollView.

I have a LazyVGrid inside a ScrollView with a single adaptive column.

import SwiftUI

struct MyView: View {
    var numbers = 1...30
    
    var body: some View {
        let columns = [
            GridItem(.adaptive(minimum: 50), spacing: 5)
        ]
        
        VStack {
            GeometryReader { geometry in
                ScrollView {
                    LazyVGrid(columns: columns, spacing: 5) {
                        ForEach(numbers, id: \.self) { i in
                            Text(String(i))
                                .frame(width: 50, height: 50)
                                .border(.blue)
                        }
                    }
                    .border(.red)
                    .padding()
                    .frame(minWidth: geometry.size.width, minHeight: geometry.size.height)
                }
            }
        }.frame(width: 500, height: 200)
    }
}

import PlaygroundSupport
PlaygroundPage.current.setLiveView(MyView())

This looks fine when there are a lot of items in the grid:

But when there are few items, I would prefer the items to be centered. Instead, they are left-aligned in a single row. The LazyVGrid appears to take up all the horizontal space, even if it doesn't need it.

How can I achieve horizontal centering, on iOS 14?

NB: The ScrollView may not have anything to do with the problem at hand. But I mention it because I had to use GeometryReader in order to vertically center the contents when using a ScrollView.

Share Improve this question edited Nov 21, 2024 at 19:55 Lysann Tranvouez asked Nov 21, 2024 at 17:08 Lysann TranvouezLysann Tranvouez 93610 silver badges16 bronze badges 1
  • Ths post might help: Center alignment in row of LazyVGrid – Benzy Neez Commented Nov 21, 2024 at 17:16
Add a comment  | 

1 Answer 1

Reset to default 0

I ended up making a custom AdaptiveVGrid implementation, loosely based on this tutorial: https://www.fivestars.blog/articles/adaptive-swiftui-views/

I'm very new to SwiftUI, so there's likely some issues with the implementation. Take it with a grain of salt. It's quite verbose at least.

I'll leave this question open in case anyone has a better approach.

/// A view to display items in a grid, while adapting to the available space, item size, and number of items.
/// When items can fit in one row or column, prefer that (depending on whether we have more horizontal or vertical space).
/// Otherwise uses a LazyVGrid insize a ScrollView to display the content.
///
/// content should have the number of subviews specified as numItems.
struct AdaptiveVGrid<Content: View>: View {
  var numItems: Int
  var itemMinSize: CGSize
  var itemMaxSize: CGSize
  var itemSpacing: CGFloat
  var content: Content
  
  public init(
    numItems: Int,
    itemMinSize: CGSize,
    itemMaxSize: CGSize,
    itemSpacing: CGFloat,
    @ViewBuilder content: () -> Content
  ) {
    self.numItems = numItems
    self.itemMinSize = itemMinSize
    self.itemMaxSize = itemMaxSize
    self.itemSpacing = itemSpacing
    self.content = content()
  }
  
  var body: some View {
    GeometryReader { geometry in
      bodyImpl(availableSize: geometry.size)
        .frame(minWidth: geometry.size.width, minHeight: geometry.size.height)
    }
  }
  
  @ViewBuilder
  func bodyImpl(availableSize: CGSize) -> some View {
    let widthRatio = availableSize.width / (CGFloat(numItems) * itemMaxSize.width + CGFloat(numItems - 1) * itemSpacing)
    let heightRatio = availableSize.height / (CGFloat(numItems) * itemMaxSize.height + CGFloat(numItems - 1) * itemSpacing)
    
    if widthRatio >= heightRatio && availableSize.width >= (CGFloat(numItems) * itemMinSize.width + CGFloat(numItems - 1) * itemSpacing) {
      HStack(spacing: itemSpacing) { content }
    } else if heightRatio >= widthRatio && availableSize.height >= (CGFloat(numItems) * itemMinSize.height + CGFloat(numItems - 1) * itemSpacing) {
      VStack(spacing: itemSpacing) { content }
    } else {
      ScrollView {
        let columns = [GridItem(.adaptive(minimum: itemMinSize.width, maximum: itemMaxSize.width), spacing: itemSpacing)]
        LazyVGrid(columns: columns, alignment: .center) {
          content
        }
        .frame(minWidth: availableSize.width, minHeight: availableSize.height)
      }
    }
  }
}

struct MyView: View {
  @ObservedObject var viewModel: MyModel
  @ScaledMetric var accessabilityScale: CGFloat = 1
  
  @State private var availableSize: CGSize = .zero
  private let itemMinSizeBase = CGSize(width: 100, height: 100)
  private let itemMaxSizeBase = CGSize(width: 250, height: 250)
  private let itemSpacingBase = 20.0
  
  var body: some View {
    GeometryReader { geometry in
      AdaptiveVGrid(
        numItems: viewModel.items.count,
        itemMinSize: adjustSize(itemMinSizeBase, availableSize: geometry.size),
        itemMaxSize: adjustSize(itemMaxSizeBase, availableSize: geometry.size),
        itemSpacing: itemSpacingBase * accessabilityScale
      ) {
        ForEach(viewModel.items, id: \.id) { item in
          itemView(item: item)
            .frame(minWidth: adjustSize(itemMinSizeBase, availableSize: geometry.size).width,
                   maxWidth: CGFloat.greatestFiniteMagnitude,
                   minHeight: adjustSize(itemMinSizeBase, availableSize: geometry.size).height,
                   maxHeight: adjustSize(itemMaxSizeBase, availableSize: geometry.size).height
            )
        }
        .padding()
      }
    }
  }
  
  private func adjustSize(_ size: CGSize, availableSize: CGSize) -> CGSize {
    CGSize(width: min(size.width, availableSize.width),
           height: min(size.height * accessabilityScale, availableSize.height))
  }
}

本文标签: swiftHorizontally center contents of LazyVGrid inside ScrollView with adaptive columnsStack Overflow