FlowStack.swift 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import SwiftUI
  2. @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
  3. struct FlowStack<Content>: View where Content: View {
  4. // The number of columns we want to display
  5. var columns: Int
  6. // The total number of items in the stack
  7. var numItems: Int
  8. // The alignment of our columns in the last row
  9. // when they don't fill all the column slots
  10. var alignment: HorizontalAlignment
  11. let content: (Int, CGFloat) -> Content
  12. private func width(for size: CGSize) -> CGFloat {
  13. size.width / CGFloat(columns)
  14. }
  15. private func index(forRow row: Int, column: Int) -> Int {
  16. (row * columns) + column
  17. }
  18. private var lastRowColumns: Int { numItems % columns }
  19. private var rows: Int { numItems / columns }
  20. init(
  21. columns: Int,
  22. numItems: Int,
  23. alignment: HorizontalAlignment?,
  24. @ViewBuilder content: @escaping (Int, CGFloat) -> Content
  25. ) {
  26. self.content = content
  27. self.columns = columns
  28. self.numItems = numItems
  29. self.alignment = alignment ?? HorizontalAlignment.leading
  30. }
  31. var body: some View {
  32. // A GeometryReader is required to size items in the scroll view
  33. GeometryReader { geometry in
  34. // Assume a vertical scrolling orientation for the grid
  35. ScrollView(Axis.Set.vertical) {
  36. // VStacks are our rows
  37. VStack(alignment: self.alignment, spacing: 0) {
  38. ForEach(0 ..< self.rows) { row in
  39. // HStacks are our columns
  40. HStack(spacing: 0) {
  41. ForEach(0 ..< self.columns) { column in
  42. self.content(
  43. // Pass the index to the content
  44. self.index(forRow: row, column: column),
  45. // Pass the column width to the content
  46. self.width(for: geometry.size)
  47. )
  48. // Size the content to frame to fill the column
  49. .frame(width: self.width(for: geometry.size))
  50. }
  51. }
  52. }
  53. // Last row
  54. // HStacks are our columns
  55. HStack(spacing: 0) {
  56. ForEach(0 ..< self.lastRowColumns) { column in
  57. self.content(
  58. // Pass the index to the content
  59. self.index(forRow: self.rows, column: column),
  60. // Pass the column width to the content
  61. self.width(for: geometry.size)
  62. )
  63. // Size the content to frame to fill the column
  64. .frame(width: self.width(for: geometry.size))
  65. }
  66. }
  67. }
  68. }
  69. }
  70. }
  71. }