iPad 弹出窗口中的 NavigationView 在 SwiftUI 中无法正常工作
NavigationView in iPad popover does not work properly in SwiftUI
我有以下代码可以在点击按钮时显示弹出窗口:
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()
}
// }
})
}
}
如果我在 popover 的内容中添加一个 NavigationView
然后我得到这个:
知道为什么会这样吗?
如果我为内容设置一个固定的框架,它工作正常,但我不想这样做,因为我希望弹出窗口根据它的内容调整大小。
可能在 iPad 他们遇到了尺寸检测的 chicken-egg 问题,所以最终确定了最小值。
无论如何,解决方案是显式设置 .frame
,要么使用预定义值(对于 iPad 来说还不错),要么使用动态计算(例如从外部框架通过 GeometryReader
)
这是一个例子。测试 Xcode 12 / iPadOS 14
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)
}
}
}
变体 2:部分根据外部尺寸计算,部分根据内部尺寸计算。
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: [=11=].frame(in: .local).size.width)
})
.onPreferenceChange(ViewWidthKey.self) {
self.popoverWidth = [=11=]
}
}
.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()
}
}
Asperi 的回答很棒而且很透彻,但我想我会为我们当中比较懒的人添加一个。
微小的弹出窗口 window 是 iPadOS 13.4 中引入的错误(弹出窗口如您在 13.0.x - 13.3.x 中所预期的那样出现)。我提交了关于它的 FB7640734,目前显示“少于 10”个类似的报告,并且仍然开放。
我在 iOS、iPadOS 和 Mac Catalyst 上用 SwiftUI 运行 编写的生产应用程序中使用的简单解决方法是在 NavigationView 之后添加:
.frame(minWidth: 320, idealWidth: 400, maxWidth: nil, minHeight: 500, idealHeight: 700, maxHeight: nil, alignment: .top)
即在 OP 示例代码的上下文中:
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)
})
}
}
这会设置一个 decently-sized 弹出框,它将在 320-400 点宽和 500x700 点高之间展开,这在实践中对于弹出框来说是一个很好的尺寸(任何更大的尺寸,您可能应该使用除弹出框)。
我有以下代码可以在点击按钮时显示弹出窗口:
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()
}
// }
})
}
}
如果我在 popover 的内容中添加一个 NavigationView
然后我得到这个:
知道为什么会这样吗?
如果我为内容设置一个固定的框架,它工作正常,但我不想这样做,因为我希望弹出窗口根据它的内容调整大小。
可能在 iPad 他们遇到了尺寸检测的 chicken-egg 问题,所以最终确定了最小值。
无论如何,解决方案是显式设置 .frame
,要么使用预定义值(对于 iPad 来说还不错),要么使用动态计算(例如从外部框架通过 GeometryReader
)
这是一个例子。测试 Xcode 12 / iPadOS 14
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)
}
}
}
变体 2:部分根据外部尺寸计算,部分根据内部尺寸计算。
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: [=11=].frame(in: .local).size.width)
})
.onPreferenceChange(ViewWidthKey.self) {
self.popoverWidth = [=11=]
}
}
.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()
}
}
Asperi 的回答很棒而且很透彻,但我想我会为我们当中比较懒的人添加一个。
微小的弹出窗口 window 是 iPadOS 13.4 中引入的错误(弹出窗口如您在 13.0.x - 13.3.x 中所预期的那样出现)。我提交了关于它的 FB7640734,目前显示“少于 10”个类似的报告,并且仍然开放。
我在 iOS、iPadOS 和 Mac Catalyst 上用 SwiftUI 运行 编写的生产应用程序中使用的简单解决方法是在 NavigationView 之后添加:
.frame(minWidth: 320, idealWidth: 400, maxWidth: nil, minHeight: 500, idealHeight: 700, maxHeight: nil, alignment: .top)
即在 OP 示例代码的上下文中:
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)
})
}
}
这会设置一个 decently-sized 弹出框,它将在 320-400 点宽和 500x700 点高之间展开,这在实践中对于弹出框来说是一个很好的尺寸(任何更大的尺寸,您可能应该使用除弹出框)。