最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - Is there a way to get a list to size to fit in a popover? - Stack Overflow

programmeradmin6浏览0评论

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)
    }
}

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)
    }
}
Share Improve this question asked Mar 21 at 2:19 Ser PounceSer Pounce 14.3k20 gold badges87 silver badges170 bronze badges 4
  • 1 Unrelated but is swiping in a pop-over really a good user experience? Will it feel like it’s a natural thing to do? – Joakim Danielson Commented Mar 21 at 8:28
  • @JoakimDanielson the only other alternative i can think of is putting an edit button on the pop-over at the bottom of the list that brings up a .sheet that allows the user to edit the list. If you look at Benzy Neez's solution below, it leaves plenty of space for swiping, i don't see why it wouldn't be a good experience. – Ser Pounce Commented Mar 22 at 5:46
  • It’s not about the space but how the user experiences the UI. I think for many users, even experienced ones, it won’t feel like a natural thing to do to swipe to delete in a pop up so they won’t understand that they can do this. But maybe it’s just me and for everyone else this is fine… – Joakim Danielson Commented Mar 22 at 8:18
  • @JoakimDanielson that's a fair point. how would you do it in this situation, if an item has a list of tags and you want the user to be able to delete them? only things i can think of are putting a trash can button at the far end of each row or an edit button on the very last row that pops up a sheet. – Ser Pounce Commented Mar 22 at 18:06
Add a comment  | 

2 Answers 2

Reset to default 1

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.

  • The height of the list can be computed from the full height of one row and the number of rows.

  • The full height of a row can be measured by attaching the .onGeometryChange modifier to the row background. Only the first row needs to be measured.

  • The width of the list should be determined by the widest row content. This means measuring the width of each row.

  • Since the width of the content does not include the list row insets, you will probably want to add some reserve width to allow space for the insets. Alternatively, you could set the list row insets to 0 and add padding to the content instead. However, this will affect the way the row separators are aligned.

  • Apply .fixedSize() to the text, to prevent it from wrapping.

  • The popover is initially sized based on the ideal size of its content and the ideal size of a List is normally very small. So it is important to set a sensible idealWidth and idealHeight on the list. These values actually represent a maximum limit on the width and height, once the measured sizes are applied.

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
        }
    }
}

You may want to replace List with ScrollView, then it will calculate its content height automatically, without providing .frame:

//List {
ScrollView { //<- here
    ForEach(names, id: \.self) { name in
        Text(name).swipeActions(allowsFullSwipe: false) {
            Button {
            } label: {
                Label("Delete", systemImage: "trash")
            }
            .tint(.red)
        }
    }
}

发布评论

评论列表(0)

  1. 暂无评论