使用 .edgesIgnoringSafeArea() 时,SwiftUI 布局超出设备范围

Posted

技术标签:

【中文标题】使用 .edgesIgnoringSafeArea() 时,SwiftUI 布局超出设备范围【英文标题】:SwiftUI layout grows outside the bounds of the device when using .edgesIgnoringSafeArea() 【发布时间】:2020-04-28 01:40:44 【问题描述】:

在使用 .edgesIgnoringSafeArea(.bottom) 时,在 SwiftUI 中存在一个问题,即某些视图在垂直方向上的增长大于设备的大小。在 812 像素高的 iPhone 11 Pro 上,我看到大小为 846 的视图。我正在使用 Debug View Hierarchy 来验证它。这已经在 Xcode 11.4.1 和 11.1 上进行了测试,并且存在于两个版本中,并且可能介于两者之间。

我在下面包含了示例代码。

我很确定这是一个 SwiftUI 错误,但想知道是否有人有解决方法。我需要edgesIgnoringSafeArea(.bottom) 代码来绘制TabBar,并在我隐藏自定义标签栏时让ProfileView() 扩展到屏幕底部。

struct ContentView: View 
    var body: some View 
        MainTabView()
    


struct MainTabView : View 

    enum Item : CaseIterable 
        case home
        case resources
        case profile
    

    @State private var selected : Item = .home

    var body: some View 
        VStack(spacing: 0.0) 
            ZStack 
                HomeView()
                    .zIndex(selected == .home ? 1 : 0)
                
                ResourcesView()
                    .zIndex(selected == .resources ? 1 : 0)

                ProfileView()
                    .zIndex(selected == .profile ? 1 : 0)
            
          
            // Code here for building and showing/hiding a Toolbar
            // Basically just a HStack with a few buttons in it
        
        .edgesIgnoringSafeArea(.bottom) // <- This causes the screen to jump to 846
    


struct ProfileView : View 
    @State private var showQuestionnaireView = false

    var body: some View 
        NavigationView 
            ZStack 

                NavigationLink(destination: QuestionnaireView( showQuestionnaireView:$showQuestionnaireView),
                               isActive: $showQuestionnaireView) 
                                Text("Show Questionnaire View")
                
                .navigationBarTitle("")
                .navigationBarHidden(true)
            
        
    


struct QuestionnaireView : View 
    @Binding var showQuestionnaireView : Bool

    var body: some View 
        GeometryReader  screenGeometry in
            ZStack 
                Color.orange
                VStack 
                    Text("Top")
                    Spacer()
                    Text("Bottom")
                
            
        
    

HomeView() 和 ResourcesView() 只是 ProfileView() 的副本,它们做自己的事情。

当你运行它时,你会看到一个按钮,按下按钮,一个隐藏的 Navigation Stack View 会推送到 QuestionnaireView 上,这个视图包含一个带有两个文本字段的 VStack,由于这个问题,你都看不到.可以理解的是,顶部位于缺口后面,但底部位于屏幕底部。在我的真实项目中,这个问题在运行时很少出现,但是在暗模式和亮模式之间切换会显示出来。在上面的代码中,不需要切换外观。

编辑:FB7677794 对于任何感兴趣的人,自 3 周前提交以来尚未收到 Apple 的任何更新。

EDIT2:向 MainTabBar 添加了更多代码

更新:这已在 Xcode 12 Beta 2 中修复

【问题讨论】:

【参考方案1】:

阅读更新后的问题后,我做了一些更改并尝试制作一个小演示。在此,我使用与以前相同的方法,将NavigationView 放在您的主标签视图中,这样您就不必每次来或离开主标签视图时都隐藏和显示。

import SwiftUI

struct ContentView: View 
    var body: some View 
        MainTabView()
    


struct MainTabView : View 

    enum Item : CaseIterable 
        case home
        case resources
        case profile
    

    @State private var selected : Item = .home

    var body: some View 
        NavigationView 
            VStack(spacing: 0.0) 
                ZStack 
                    Group 
                        HomeView()
                            .zIndex(selected == .home ? 1 : 0)

                        ResourcesView()
                            .zIndex(selected == .resources ? 1 : 0)

                        ProfileView()
                            .zIndex(selected == .profile ? 1 : 0)

                    
                    .frame(minWidth: .zero, maxWidth: .infinity, minHeight: .zero, maxHeight: .infinity)
                    .background(Color.white)

                

                HStack 
                    Group 
                        Image(systemName: "house.fill")
                            .onTapGesture 
                                self.selected = .home
                        

                        Spacer()

                        Image(systemName: "plus.app.fill")
                            .onTapGesture 
                                self.selected = .resources
                        

                        Spacer()

                        Image(systemName: "questionmark.square.fill")
                            .onTapGesture 
                                self.selected = .profile
                        
                    
                    .padding(.horizontal, 30)
                
                .frame(height: 40)
                .foregroundColor(Color.white)
                .background(Color.gray)


                // Code here for building and showing/hiding a Toolbar
                // Basically just a HStack with a few buttons in it
            
            .edgesIgnoringSafeArea(.bottom)
         // <- This causes the screen to jump to 846
    


struct ProfileView : View 
    @State private var showQuestionnaireView = false

    var body: some View 
//        NavigationView 
            ZStack 

                NavigationLink(destination: QuestionnaireView( showQuestionnaireView:$showQuestionnaireView),
                               isActive: $showQuestionnaireView) 
                                Text("Show Questionnaire View")
                
                .navigationBarTitle("")
                .navigationBarHidden(true)

            

//        
    


struct QuestionnaireView : View 
    @Binding var showQuestionnaireView : Bool

    var body: some View 
        GeometryReader  screenGeometry in
            ZStack 
                Color.orange
                VStack 
                    Text("Top")
                    Spacer()
                    Text("Bottom")
                
            
            .edgesIgnoringSafeArea(.bottom)
        
    


struct HomeView: View 
    var body: some View 
        NavigationLink(destination: SecondView()) 
            Text("Home View")
        
    


struct ResourcesView: View 
    var body: some View 
        NavigationLink(destination: SecondView()) 
            Text("Resources View")
        
    


struct SecondView: View 
    var body: some View 
        Text("Second view in navigation")
            .background(Color.black)
            .foregroundColor(.white)
    



struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
        .previewDevice(PreviewDevice(rawValue: "iPhone 11"))
    

【讨论】:

我不能这样做,因为 MainTabView 比我发布的代码更复杂。它是一个包含 3 个主屏幕的 TabView,可以像普通的 Tab View 一样在它们之间切换(只有我的是自定义的,所以我可以隐藏工具栏)。这些主屏幕中的每一个都有自己的 NavigationView。在只发布您认为相关的代码和在问题中倾倒一大堆代码之间,这是一条很好的界限。我会尝试在问题中向 MainTabView 添加更多代码。 我对我的答案做了一些修改,我希望这就是你想要的。 谢谢 Nilay,这种方式确实有效,感谢您在我更新问题后花时间更新您的答案。我已经给你赏金了。 仅供参考,我发布的原始代码适用于 Xcode 12 Beta 2,Apple 表示他们已经解决了这个问题。但是现在顶部标签从顶部向下大约 44.0 像素 ? 我仍然在我的应用程序中使用你的解决方法。 很高兴能为您提供帮助。【参考方案2】:

这是由于NavigationView 的大小未定义。当您添加自定义标签栏组件时,如下例所示,限制底部区域,NavigationView 将正确布局。

使用 Xcode 11.4 / ios 13.4 测试

struct MainTabView : View 
    var body: some View 
        VStack(spacing: 0.0) 
            ZStack 
                Color(.cyan)
                ProfileView()   // << this injects NavigationView
            
            HStack  // custom tab bar
                Button(action: )  Image(systemName: "1.circle").padding() 
                Button(action: )  Image(systemName: "2.circle").padding() 
                Button(action: )  Image(systemName: "3.circle").padding() 
            .padding(.bottom)
        
        .edgesIgnoringSafeArea(.bottom) // works !!
    

【讨论】:

感谢@Asperi 的快速回复。我希望这个问题比上一个问题(我删除了)更好地提出。所以我有非常相似的代码(我拿出来发布),它显示了 TabBar。我制作自己的标签栏的原因是因为 SwiftUI 无法隐藏。因此,当我在“个人资料”屏幕上并按下问卷按钮时,我会隐藏我的 TabBar,以便下一个屏幕具有全高。然后屏幕尺寸问题变得明显。你能详细说明为什么 NavigationView 在这里有一个未定义的大小吗?它不应该在屏幕尺寸上达到最大值吗?

以上是关于使用 .edgesIgnoringSafeArea() 时,SwiftUI 布局超出设备范围的主要内容,如果未能解决你的问题,请参考以下文章

在我的 NavigationView '.edgesIgnoringSafeArea' 中不会将内容移动到安全区域之外

SwiftUI - edgesIgnoringSafeArea 在 iOS 13.4 中的行为不同

如何覆盖edgesIgnoringSafeArea(或任何视图修改器)

未通过SwiftUI从资产加载颜色

SwiftUI 列表颜色背景

如何在swiftUI中删除顶部安全区域