1

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) } } 
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? 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. 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… 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. Commented Mar 22 at 18:06

2 Answers 2

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

Animation

Sign up to request clarification or add additional context in comments.

2 Comments

Awesome solution, thank you so much. I tried setting .frame(idealWidth: 400, idealHeight: 333) to listWidth and listHeight and it seemed to work fine. Do you see any issue with that down the road?
The ideal width is effectively the min width, don't expect issues there, but the ideal height is effectively the max height. In my tests, the List doesn't scroll when embedded in a popover, so you need to make sure that you never exceed the number of rows that fit within this height. Of course, Apple may change the way it works in a future iOS release, so if you really wanted to be safe you could consider using a custom popover instead. Ps. thanks for accepting the answer!
0

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

enter image description here

2 Comments

The swipeActions won't work on ScrollView, only can get them to work on List.
you could make the real list an overlay. Probably will take a lot of effort to have the sizes match up though.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.