Skip to content
This repository has been archived by the owner on Nov 4, 2022. It is now read-only.

dynamic height in horizontally scrollable section #186

Open
seboslaw opened this issue Oct 4, 2020 · 2 comments
Open

dynamic height in horizontally scrollable section #186

seboslaw opened this issue Oct 4, 2020 · 2 comments

Comments

@seboslaw
Copy link

seboslaw commented Oct 4, 2020

Hey guys,

I'm basically trying to implement the AppStore example (vertical scroll view with horizontally scrollable sections) but with dynamic type support. In order for this to work I've modified the var layout: ASCollectionLayout<Int> extension to deliver the following code:

			default:
				return ASCollectionLayoutSection
				{ environment in					
					let itemSize = NSCollectionLayoutSize(
						widthDimension: .fractionalWidth(1),
						heightDimension: .estimated(350))
					
					let item = NSCollectionLayoutItem(layoutSize: itemSize)
				
					let groupSize = NSCollectionLayoutSize(
						widthDimension: .fractionalWidth(1),
						heightDimension: .estimated(350))
					
					let group = NSCollectionLayoutGroup.horizontal(
						layoutSize: groupSize, 
						subitems: [item])
					
					let headerSize = NSCollectionLayoutSize(
						widthDimension: .fractionalWidth(1.0),
						heightDimension: .estimated(44))
					
					
					let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
						layoutSize: headerSize,
						elementKind: UICollectionView.elementKindSectionHeader, 
						alignment: .top)

					let section = NSCollectionLayoutSection(group: group)
					section.boundarySupplementaryItems = [sectionHeader]
					section.orthogonalScrollingBehavior = .continuous
					section.interGroupSpacing = 12
					
					return section

However, the .estimated(350) for the item's and group's height don't seem to work. The resulting section always has a height of 350 and doesn't adopt to the actual content's height. The actual cell content has a blue background for debugging purposes - when you scroll sideways you get the next item...in the next release these cells should only be 1/3 of the screen's width. The white area underneath the blue cell however should actually shrink.

Screenshot 2020-10-04 at 17 59 05

Cheers,
Sebastian

@apptekstudios
Copy link
Owner

Apple's UICollectionViewCompositionalLayout (which is what you're using here) seems to struggle with this type of sizing. You'll notice that where they use it on the app store and elsewhere they actually use a fixed size. Curiously, none of their demos show the estimated sizing being used either. I'm not sure how to fix this other than to use a fixed size sorry!

@andrei1152
Copy link

andrei1152 commented Feb 16, 2021

I think I've solved this, only be mere chance and with a lot of extra additional help.

image

There's a lot of things that I've done to make this work, and I don't know which of them will help you.

This is how I'm using ASCollectionView to create a horizontally scrollable list of cards. The .dynamicHorizontalList function is similar to what you've used for layout, in that it allows you to set values for height as well, instead of .fractional(1) value offered with the .list() method

ASCollectionView(data: stores) {store,_ in
            StoreCard(store: store)
        }.layout(scrollDirection: .horizontal) { sectionId in
            .dynamicHorizontalList(itemWidth: .fractionalWidth(0.42), itemHeight: .estimated(200), spacing: 10, sectionInsets: .init(top: 0, leading: AppSpacings.large, bottom: 0, trailing: 0))
        }
        .scrollIndicatorsEnabled(horizontal: false)
        .fitContentSize(dimension: .vertical)
        .padding(.bottom, AppSpacings.large)
        .listRowInsets(EdgeInsets())

Another thing to note is that I've used a custom HorizontalGeometryReader to compute the height of the cell, which only takes the width value of the parent and does not scale on the y-axis like the usual GeometryReader.

var body: some View {
        HorizontalGeometryReader { width in
            VStack(alignment: .leading) {
                ZStack(alignment: .bottom) {
                    KFImage(store.storeImageURL)
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: width, height: width * 1.4)
                        .cornerRadius(20)
                        .clipped()
                    KFImage(store.storeLogoIconURL)
                        .resizable()
                        .frame(width: 80, height: 80)
                        .aspectRatio(contentMode: .fill)
                        .clipShape(Circle())
                        .offset(y: -AppSpacings.small)
                }
                HStack {
                    Image("time")
                    Text(estimatedTime)
                    Spacer()
                    Image("car")
                    Text(shippmentPrice)
                }
                    .font(.regularSmall)
                    .accentColor(.appPrimary)
                
                Text(store.storeName).font(.semiboldMedium)
                Spacer()
            }
        }
    }

The final sauce that made this work, which is something that I've discovered by accident, was adding the Spacer() in the VStack that I've used for the card in the code above. Without this, the card will not grow to fit its content, but will instead collapse along its y-axis. I don't understand why, but oh well.

Also, here's the code for the HorizontalGeometryReader class.

import SwiftUI

struct WidthReader : PreferenceKey, Equatable {
    static var defaultValue: CGFloat { 10 }

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = max(value, nextValue())
    }
}


struct WidthReaderView : View {
    var body: some View {
        GeometryReader { geometry in
            Color.clear.preference(key: WidthReader.self, value: geometry.size.width)
        }
    }
}

public struct HorizontalGeometryReader<Content: View> : View {
    
    var content: (CGFloat)->Content
    @State private var width: CGFloat = WidthReader.defaultValue
    
    public init(@ViewBuilder content: @escaping (CGFloat)->Content) {
        self.content = content
    }
    
    public var body: some View {
        content(width)
            .frame(minWidth: 0, maxWidth: .infinity)
            .background(WidthReaderView())
            .onPreferenceChange(WidthReader.self) { width in
                self.width = width
            }
    }
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants