0

I would like to create a layout exactly like the iPhone emoji keyboard one. So basically a scroll view that has a grid with pinned views each with an header on top.
Note: the solution has to work on iOS 14+.

I looked at this thread where the person has my same problem but it hasn't been solved.
After trying their solution I tried to create a manual one. As of my understanding, the other person's problem is caused by the header being inside of the HGrid, since in a horizontal grid it won't be placed on top because the items are placed horizontally. I noticed that putting a section inside a VStack, makes the header appear on top. Now, the problem is that I want the vertical section behavior inside a LazyHGrid, because I want to achieve this layout:

Desired layout

Where I have a header on top of each section of the grid and it is pinned to them, so that when I scroll, it always stays above the content.

The closest I came to the solution was by doing what I demonstrate in the following block of code. Anyway, while I solved the header placement, I couldn't manage to make it always appear on top of the current section

 let arr2 = [[[1,2,3,4,5,1,2,3,4,5,1,2,3,4,5],[1,2,3,4,6,1,2,3,4,5,1,2,3,4,5],[1,2,3,4,7,1,2,3,4,5,1,2,3,4,5],[1,2,3,4,8,1,2,3,4,5,1,2,3,4,5],[1,2,3,4,9,1,2,3,4,5]],
                            [[5,2,3,4,5],[1,2,3,4,6],[1,2,3,4,7],[1,2,3,4,8],[1,2,3,4,9]],
                            [[8,2,3,4,5],[1,2,3,4,6],[1,2,3,4,7],[1,2,3,4,8],[1,2,3,4,9]]]
                
                ScrollView(.horizontal) {
                    LazyHStack(pinnedViews: [.sectionHeaders]){
                        ForEach(arr2, id: \.self ) { arrr in
                            VStack(alignment: .leading){
                                Section {
                                    ForEach(arrr, id: \.self) { sett in
                                        HStack {
                                            ForEach(sett, id: \.self) { e in
                                                Text("\(e)")
                                                    .font(.title2)
                                            }
                                        }
                                    }
                                }
                            header: {
                                Text("Header")
                            }
                            }
                        }
                    }
                }

How can I recreate a layout that has a scrollView that scrolls horizontally with multiple sections inside each labelled by a header that is pinned to its content?

UPDATE:

I went back to the solution presented in the linked question and I set an Offset for the grid. however, now there is a new problem: Despite the header is effectively above the grid, there is a wide lateral space on the right that matches the width of the header. The even bigger problem is that when we have big datasets, the grid is partially cut and the content is not loaded in that space unless you scroll it. So, while this partially looks good, I don't think this solution is acceptable.

 let arr3 = [Array(0...20),Array(21...31),Array(32...48),Array(49...67),Array(68...90)]
                
                                ScrollView(.horizontal) {
                
                                    LazyHGrid(rows: Array(repeating: GridItem(.fixed(40), spacing: 0, alignment: .topLeading), count: 5), alignment: .top, spacing: 0, pinnedViews: [.sectionHeaders] ) {
                                        
                                        ForEach(arr3.indices, id: \.self) { index in
                                            
                                            Section {
                                                ForEach(arr3[index], id: \.self) { number in
                                                    ZStack {
                                                        Rectangle()
                                                            .fill(.red)
                                                        
                                                        Button {
                                                            //...
                                                        } label: {
                                                            Text(String(number))
                                                                .font(.system(size: 20))
                                                                .foregroundColor(.black)
                                                                .padding(.horizontal, 2)
                                                        }
                                                    }
                                                }
                                                .offset(x: -("My Header \(index)").calculateWidth(font: UIFont.systemFont(ofSize: 14)), y:12)
                                            } header: {
                                                VStack{
                                                    Text("My Header \(index)")
                                                        .frame(height: 12)
                                                        .font(.caption)
                                                        .textCase(.uppercase)
                                                        .opacity(0.4)
                                                        .background(.green.opacity(0.4))
                                                    
                                                }
                                                
                                                
                                            }
                                        }
                                        
                                    }
                                }
                

The function I use to calculate the width of the string is the following:

extension String {
    func calculateWidth(font: UIFont) -> CGFloat{
        let messageString = self
        let attributes : [NSAttributedString.Key : Any] = [NSAttributedString.Key(rawValue:
                                                                                    NSAttributedString.Key.font.rawValue) : font]
        
        let attributedString : NSAttributedString = NSAttributedString(string: messageString, attributes: attributes)
        let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude),
                                                          options: .usesLineFragmentOrigin, context: nil)
        
        let requredSize:CGRect = rect
        return requredSize.width
    }
}

Here you can see the big space on the right enter image description here

crost
  • 165
  • 1
  • 13

0 Answers0