Reputation: 11
I want to design the Horizontal Grid based on condition I need when data comes from API when meet "HEADING" then should be only one cell in the whole row otherwise 4 cells.
This is my required view:
And this is my approach: **Nte the Fetched Response is "evaluationKeysData": [ { "id": "basic", "description": "HEADING", "str_sort_order": "001" },
{
"id": "arm_left",
"description": "Arm - Left",
"str_sort_order": "010"
},
{
"id": "wingspan",
"description": "Wingspan",
"str_sort_order": "011"
},
{
"id": "measurement",
"description": "HEADING",
"str_sort_order": "012"
},
{
"id": "neck",
"description": "Neck",
"str_sort_order": "013"
},
{
"id": "chest",
"description": "Chest",
"str_sort_order": "014"
},
{
"id": "hip",
"description": "Hip",
"str_sort_order": "015"
},
{
"id": "waist",
"description": "Waist",
"str_sort_order": "016"
},
{
"id": "thigh_right",
"description": "Thigh - Right",
"str_sort_order": "017"
},
{
"id": "bicep_right",
"description": "Bicep - Right",
"str_sort_order": "018"
},
{
"id": "hinge",
"description": "Hinge",
"str_sort_order": "019"
},
{
"id": "bilateral",
"description": "Bilateral",
"str_sort_order": "020"
},
{
"id": "r_abduction",
"description": "R - Abduction",
"str_sort_order": "027"
},
{
"id": "l_abduction",
"description": "L - Abduction",
"str_sort_order": "028"
},
{
"id": "vertical",
"description": "Vertical",
"str_sort_order": "039"
},
{
"id": "reaction",
"description": "Reaction",
"str_sort_order": "040"
},
{
"id": "10_yd",
"description": "10 yd",
"str_sort_order": "041"
},
{
"id": "15_ft",
"description": "15 ft",
"str_sort_order": "042"
},
{
"id": "20_yd",
"description": "20 yd",
"str_sort_order": "043"
},
{
"id": "30_ft",
"description": "30 ft",
"str_sort_order": "044"
},
{
"id": "mph",
"description": "MPH",
"str_sort_order": "045"
},
{
"id": "40_yd",
"description": "40 yd",
"str_sort_order": "046"
},
{
"id": "60_yd",
"description": "60 yd",
"str_sort_order": "047"
},
{
"id": "75_ft",
"description": "75 ft",
"str_sort_order": "048"
},
{
"id": "adjusted",
"description": "Adjusted",
"str_sort_order": "049"
},
{
"id": "fly_5_/_10",
"description": "Fly 5 / 10",
"str_sort_order": "050"
},
{
"id": "flying_10_/_10",
"description": "Flying 10 / 10",
"str_sort_order": "051"
},
{
"id": "half",
"description": "Half",
"str_sort_order": "052"
},
{
"id": "full",
"description": "Full",
"str_sort_order": "053"
},
{
"id": "cmj_height",
"description": "CMJ - Height",
"str_sort_order": "054"
},
{
"id": "broad_jump",
"description": "Broad Jump",
"str_sort_order": "060"
},
{
"id": "l_drill_half",
"description": "L - Drill - Half",
"str_sort_order": "061"
},
{
"id": "x-drill",
"description": "X-Drill",
"str_sort_order": "062"
},
{
"id": "measurement_3",
"description": "HEADING",
"str_sort_order": "063"
},
{
"id": "row_2000_m",
"description": "Row - 2000 m",
"str_sort_order": "064"
},
{
"id": "row_500_m",
"description": "Row - 500 m",
"str_sort_order": "065"
},
{
"id": "row_power",
"description": "Row - Power",
"str_sort_order": "066"
},
{
"id": "hr_pre",
"description": "HR - Pre",
"str_sort_order": "067"
}
]**
ScrollView{
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())], spacing: 2) {
ForEach(bleGroupMeta.fetchedData) { index in
if index.desc != "HEADING" {
TextField("", text: Binding(
get: { userMeasurements[index.id].stringValue },
set: { newValue in
userMeasurements[index.id] = JSON(newValue)
let measurement = Measurement(measurement: index.id, value: newValue)
print(measurement)
if !modifiedMeasurements.contains(measurement) {
modifiedMeasurements.append(measurement)
}
}
) , prompt: Text(index.desc)
.font(Font.custom("Agency FB", size: 16))
.foregroundColor(Constants.White.opacity(0.8)))
.padding(.leading)
.keyboardType(.decimalPad)
.font(Font.custom("Agency FB", size: 16))
.foregroundColor(Constants.White.opacity(0.8))
.background(Color.clear)
.frame(height: 40)
.frame(maxWidth: .infinity)
.cornerRadius(4)
.overlay(
RoundedRectangle(cornerRadius: 4)
.inset(by: 0.5)
.stroke(Color(red: 0.26, green: 0.27, blue: 0.3), lineWidth: 1)
)
} else {
// Add the design for the heading
VStack {
HStack(spacing: 20){
Rectangle().fill(Color.gray).frame(height: 1)
// Spacer()
Text("Heading").font(Font.custom("Agency FB", size: 16)).foregroundColor(Constants.Orange)
// Spacer()
Rectangle().fill(Color.gray).background(Color.gray).frame(height: 1)
}
}.frame(maxWidth: .infinity)
}
}.padding()
}
}
Upvotes: 0
Views: 307
Reputation: 157
You can use the TokenLayout
class to achieve a grid view. Change the width and spacing calculation according to your needs. I hope this solves your issue.
TokenLayout source code for GridView
struct TokenLayout: Layout {
var alignment: Alignment = .center
var horizontalSpacingBetweenItem: CGFloat = 10
var verticalSpacingBetweenItem: CGFloat = 0
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let maxWidth = proposal.width ?? 0
var totalHeight: CGFloat = 0
let rows = generateRows(maxWidth, proposal, subviews)
for (index, _) in rows.enumerated() {
totalHeight += rows[index].maxHeight(proposal)
totalHeight += index == rows.count - 1 ? 0 : verticalSpacingBetweenItem
}
return CGSize(width: maxWidth, height: totalHeight)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
var origin = CGPoint(x: bounds.origin.x, y: bounds.origin.y)
let maxWidth = bounds.width
let rows = generateRows(maxWidth, proposal, subviews)
for (index, row) in rows.enumerated() {
let totalWidth = row.reduce(0) { partialResult, view in
let width = view.sizeThatFits(proposal).width
return partialResult + width
}
let totalSpacing = CGFloat(row.count - 1) * horizontalSpacingBetweenItem
let remainingSpace = maxWidth - totalWidth - totalSpacing
switch alignment {
case .leading:
origin.x = bounds.minX
case .trailing:
origin.x = bounds.maxX - (totalWidth + totalSpacing)
case .center:
origin.x = bounds.minX + remainingSpace / 2
default:
break
}
for view in row {
let viewSize = view.sizeThatFits(proposal)
view.place(at: origin, proposal: proposal)
origin.x += (viewSize.width + horizontalSpacingBetweenItem)
}
origin.y += (row.maxHeight(proposal) + (index == rows.count - 1 ? 0 : verticalSpacingBetweenItem))
}
}
func generateRows(_ maxWidth: CGFloat, _ proposal: ProposedViewSize, _ subviews: Subviews) -> [[LayoutSubviews.Element]] {
var row: [LayoutSubviews.Element] = []
var rows: [[LayoutSubviews.Element]] = []
var origin = CGRect.zero.origin
for view in subviews {
let viewSize = view.sizeThatFits(proposal)
let totalWidth = origin.x + viewSize.width + (row.isEmpty ? 0 : horizontalSpacingBetweenItem)
if totalWidth > maxWidth {
rows.append(row)
row.removeAll()
origin.x = 0
}
row.append(view)
origin.x += (viewSize.width + horizontalSpacingBetweenItem)
}
if !row.isEmpty {
rows.append(row)
}
return rows
}
}
extension Array where Element == LayoutSubviews.Element {
func maxHeight(_ proposal: ProposedViewSize) -> CGFloat {
return self.compactMap { view in
return view.sizeThatFits(proposal).height
}.max() ?? 0
}
}
Example: How to use:
import SwiftUI
struct MyView: View {
let itemsPerRow = 4
let numberOfItem = 13
let horizontalSpacingBetweenItem: CGFloat = 15
var body: some View {
TokenLayout(alignment: .leading, horizontalSpacingBetweenItem: horizontalSpacingBetweenItem, verticalSpacingBetweenItem: 20) {
ForEach(0..<numberOfItem) { row in
Rectangle()
.frame(width: ((UIScreen.main.bounds.width - 50) - CGFloat(itemsPerRow - 1) * horizontalSpacingBetweenItem) / CGFloat(itemsPerRow),
height: 40)
.foregroundColor(.clear)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(Color.gray, lineWidth: 1)
)
.overlay(
Text("Text")
.foregroundColor(.black)
.padding(8)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading)
)
}
}
.padding(.horizontal, 15)
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView()
}
}
Upvotes: 0
Reputation: 636
You can call gridSwitch() function and set gridLayout with gridColumn.
@State private var gridLayout: [GridItem] = [GridItem(.flexible())]
@State private var gridColumn: Int = 1
func gridSwitch(isHeadingType: Bool) {
gridColumn = isHeadingType ? 1 : 4
gridLayout = Array(repeating: .init(.flexible()), count: gridColumn)
gridColumn = gridLayout.count
}
ScrollView(.vertical, showsIndicators: false) {
LazyVGrid(columns: gridLayout, alignment: .center, spacing: 10) {
ForEach(bleGroupMeta.fetchedData) { item in
NavigationLink(destination: DetailView(gridItem: item)) {
DetailItemView(gridItem: item)
}
}//: Loop
}//: Grid
}
Upvotes: 0