iosswiftswiftuipopover

Is there a way to get a list to size to fit in a popover?


In SwiftUI on iOS, I have some data that I want to display in a popover. It has to be in a list because I want it to be able to utilize swipeActions. The problem is that for some reason, lists don't automatically size to fit in a popover, you have to manually resize them in order to get it to work. I've tried using GeometryReader, fixedSize, setting the frame width and height to infinity, etc and nothing has worked. It only sizes to fit if it's a ForEach without a list or in a scrollview, but then the swipeActions don't work. Is there a solution that exists so that I don't have to manually set the width and height in order for it to size correctly? Here is the code:

import SwiftUI

struct MainView: View {
    @State private var popoverIsPresented: Bool = false
    
    var body: some View {
        Button {
            popoverIsPresented = true
        } label: {
            Label("Plus", systemImage: "plus")
        }
        .popover(isPresented: $popoverIsPresented) {
            ListView().presentationCompactAdaptation(.popover)
        }
    }
}

struct ListView: View {
    @State private var names: [String] = ["User 1", "User 2", "User 3", "User 4"]
    
    var body: some View {
        List {
            ForEach(names, id: \.self) { name in
                Text(name).swipeActions(allowsFullSwipe: false) {
                    Button {
                    } label: {
                        Label("Delete", systemImage: "trash")
                    }
                    .tint(.red)
                }
            }
        }
        .scrollDisabled(true)
        //.fixedSize()
        //.frame(maxWidth: .infinity, maxHeight: .infinity)
        .frame(width: 222, height: 333)
        .listStyle(.plain)
    }
}

Solution

  • One way to solve is to use .onGeometryChange to measure the size of the list content and use this to set the size of the list.

    struct ListView: View {
        @State private var names: [String] = ["User 1", "User 2", "The quick brown fox", "jumps over the lazy dog"]
        @State private var rowWidth: CGFloat?
        @State private var rowHeight: CGFloat?
    
        var body: some View {
            List {
                ForEach(Array(names.enumerated()), id: \.offset) { index, name in
                    Text(name).swipeActions(allowsFullSwipe: false) {
                        Button {
                        } label: {
                            Label("Delete", systemImage: "trash")
                        }
                        .tint(.red)
                    }
                    .fixedSize()
                    .onGeometryChange(for: CGFloat.self) { proxy in
                        proxy.size.width
                    } action: { width in
                        if width > rowWidth ?? 0 {
                            rowWidth = width
                        }
                    }
                    .listRowBackground(
                        Group {
                            if index == 0 {
                                Color.clear
                                    .onGeometryChange(for: CGFloat.self) { proxy in
                                        proxy.size.height
                                    } action: { height in
                                        rowHeight = height
                                    }
                            }
                        }
                    )
                }
            }
            .scrollDisabled(true)
            .listStyle(.plain)
            .frame(idealWidth: 400, idealHeight: 333)
            .frame(maxWidth: listWidth, maxHeight: listHeight)
        }
    
        private var listWidth: CGFloat? {
            if let rowWidth {
                rowWidth + 40 // allow for list row insets
            } else {
                nil
            }
        }
    
        private var listHeight: CGFloat? {
            if let rowHeight {
                rowHeight * CGFloat(names.count)
            } else {
                nil
            }
        }
    }
    

    Animation