JacobCXDev
JacobCXDev

Reputation: 457

SwiftUI List row's not resising to fit contents

I have a List View with 1) a header view, 2) dynamic ForEach views, and 3) a footer view. My issue is that the first row, in which the header view lies, won't resise to fit its contents. The code for the main view is below:

var body: some View {
    List {
        GeometryReader { geometry in
            self.headerView(size: geometry.size)
        }
        ForEach(self.user.posts, id: \.self) { post in
            Text(post.title)
        }
        Text("No more posts...")
            .font(.footnote)
    }
    .edgesIgnoringSafeArea(.all)
}

This is the view which I am trying to achieve:

mockup

This is what I have so far...:

actual hierarchy

If it's any consolidation, the header view displays fine if it's outside of the list, however, that's not the view I'm looking for.

Thanks in advance.

P.S: Apologies for the huge images, I'm not sure how to make them appear as thumbnails...

Upvotes: 4

Views: 4456

Answers (3)

Asi Givati
Asi Givati

Reputation: 1475

I created a simple list view that change it's height automatically with the help of "SwiftUIIntrospect" pod.

The pod:

  pod 'SwiftUIIntrospect'

ListDynamicHeight code:

import SwiftUI
import SwiftUIIntrospect

struct ListDynamicHeight<Content>:View where Content:View {
    @State private var listContentHeight:CGFloat = .zero
    /// minHeight must be >= 1
    var minHeight:CGFloat = 1
    @ViewBuilder var content:Content
    
    var body: some View {
        List {
            content
        }
        .scrollView { scroll in
            DispatchQueue.main.async {
                listContentHeight = scroll.contentSize.height
            }
        }
        .frame(minHeight: minHeight, idealHeight: listContentHeight)
    }
}

private extension View {
    func scrollView(_ scrollView: @escaping (UIScrollView) -> ()) -> some View {
        introspect(.scrollView, on: .iOS(.v15, .v16, .v17)) { scroll in
            scrollView(scroll)
        }
    }
}

#Preview {
    ListDynamicHeight(minHeight: 10) {
        Text("Hello")
    }
}

And there is also a Dynamic TextView I created (with placeholder if you like) that works perfectly with the list:

struct TextView: View {
@FocusState private var isInFocus:Bool
@Binding var text: String
var placeholder = ""
var shouldShowPlaceholder:Bool { text.isEmpty && !isInFocus }
var body: some View {
        ZStack(alignment: .topLeading) {
            if shouldShowPlaceholder {
                Text(placeholder)
                    .padding(.top, 10)
                    .padding(.leading, 6)
                    .onTapGesture {
                        isInFocus = true
                    }
            }
            
            TextEditor(text: $text)
                .colorMultiply(shouldShowPlaceholder ? .clear : .white)
                .focused($isInFocus)
        }
    }
}

Put everything together:

struct ListAndTextView:View {
    @State private var text = ""
    var placeholder:String = "Your Comment here..."
    var body: some View {
        ListDynamicHeight {
            TextView(text: $text, placeholder: placeholder)
        }
    }
}

#Preview {
    ListAndTextView()
}

Upvotes: 0

Asperi
Asperi

Reputation: 257493

I prefer to use Sections for similar purposes (sections allow to have different configuration of each), like

enter image description here

var body: some View {
    List {
        Section {
           // << header view is here with own size
        }
        .listRowInsets(EdgeInsets()) // << to zero padding

        Section { // << dynamic view is here with own settings
            ForEach(self.user.posts, id: \.self) { post in
                Text(post.title)
            }
        }
        Section { // footer view is here with own size
            Text("No more posts...")
               .font(.footnote)
        }
    }
    .edgesIgnoringSafeArea(.all)
}

Upvotes: 3

user3441734
user3441734

Reputation: 17534

Using GeometryReader is fine, but it should be used the proper way!

import SwiftUI

struct ContentView: View {
    var body: some View {
        // to get the size of view, we are going to use the width later
        GeometryReader { p in
            List {

                GeometryReader { _p in
                    // put your image or whatever ...
                    // and set the frame width
                    Color.red.frame(width: p.size.width, alignment: .center)
                }
                // and finally fix the height !! to work as expected
                .frame(height: 100)

                Text("By by, World!")
            }
        }
    }
}

Upvotes: 2

Related Questions