NavigationView in iPad popover does not work properly in SwiftUI

你说的曾经没有我的故事 提交于 2021-01-27 02:34:59

问题


I have the following code that displays a popover when a button is tapped:

struct ContentView: View {

    @State private var show = false

    var body: some View {

        Button("Open") {
            self.show.toggle()
        }.popover(isPresented: $show, content: {
//            NavigationView {
                ScrollView {
                    ForEach(0...10, id: \.self) {_ in
                        Text("Test popover ...")
                    }.padding()
                }
//            }
        })

    }
}

If I add a NavigationView in popover's content then I get this :

Any idea why this happens?

It works fine if I set a fixed frame for the content, but I do not wanna do that since I want the popover to resize according to it's content.


回答1:


Probably on iPad they've got into chicken-egg problem with size detection, so just finalised with minimum.

Anyway, the solution would be to set .frame explicitly, either with predefined values (for iPad it is not so bad), or with dynamically calculated (eg. from outer frame via GeometryReader)

Here is an example. Tested with Xcode 12 / iPadOS 14

demo

struct TestPopover: View {

    @State private var show = false

    var body: some View {
        GeometryReader { gp in
            VStack {
                Button("Open") {
                    self.show.toggle()
                }.popover(isPresented: $show, content: {
                    NavigationView {
                        ScrollView {   // or List
                            ForEach(0...10, id: \.self) {_ in
                                Text("Test popover ...")
                            }.padding()
                        }
                        .navigationBarTitle("Test", displayMode: .inline)
                    }
                    .frame(width: gp.size.width / 3, height: gp.size.height / 3)
                })
            }.frame(maxWidth: .infinity, maxHeight: .infinity)
        }
    }
}

Variant 2: Partially calculated on outer size, partially on inner size.

struct TestPopover: View {

    @State private var show = false
    @State private var popoverWidth = CGFloat(100)

    var body: some View {
        GeometryReader { gp in
            VStack {
                Button("Open") {
                    self.show.toggle()
                }.popover(isPresented: $show, content: {
                    NavigationView {
                        ScrollView {   // or List
                            ForEach(0...10, id: \.self) {_ in
                                Text("Test popover ...").fixedSize()
                            }.padding()
                            .background(GeometryReader {
                                Color.clear
                                    .preference(key: ViewWidthKey.self, value: $0.frame(in: .local).size.width)
                            })
                            .onPreferenceChange(ViewWidthKey.self) {
                                self.popoverWidth = $0
                            }
                        }
                        .navigationBarTitle("Test", displayMode: .inline)
                    }
                    .frame(width: self.popoverWidth, height: gp.size.height / 3)
                })
            }.frame(maxWidth: .infinity, maxHeight: .infinity)
        }
    }
}

struct ViewWidthKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue = CGFloat.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value += nextValue()
    }
}




回答2:


Asperi's answer is great and thorough, but thought I'd add one for the lazier among us.

The tiny popover window is a bug introduced in iPadOS 13.4 (popovers appeared as you'd expect in 13.0.x - 13.3.x). I filed FB7640734 about it, which currently shows "less than 10" similar reports and is still open.

The easy workaround, which I use in a production app written in SwiftUI running on iOS, iPadOS, and Mac Catalyst is to add this after your NavigationView:

.frame(minWidth: 320, idealWidth: 400, maxWidth: nil, minHeight: 500, idealHeight: 700, maxHeight: nil, alignment: .top)

I.e. in the context of the OP's sample code:

struct ContentView: View {

    @State private var show = false

    var body: some View {

        Button("Open") {
            self.show.toggle()
        }.popover(isPresented: $show, content: {
            NavigationView {
                ScrollView {
                    ForEach(0...10, id: \.self) {_ in
                        Text("Test popover ...")
                    }.padding()
                }
            }.frame(minWidth: 320, idealWidth: 400, maxWidth: nil,
                    minHeight: 500, idealHeight: 700, maxHeight: nil,
                    alignment: .top)
        })

    }
}

This sets a decently-sized popover that will expand between 320-400 points wide and 500x700 points high, which in practice is a good size for a popover (any larger and you probably should be using something other than a popover).



来源:https://stackoverflow.com/questions/61228002/navigationview-in-ipad-popover-does-not-work-properly-in-swiftui

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!