Brett
Brett

Reputation: 1837

SwiftUI layout grows outside the bounds of the device when using .edgesIgnoringSafeArea()

Having an issue in SwiftUI where some Views are growing bigger vertically than the size of the device when using .edgesIgnoringSafeArea(.bottom). On an iPhone 11 Pro which is 812 pixels high I am seeing a view of size 846. I am using the Debug View Hierarchy to verify it. This has been tested on Xcode 11.4.1 and 11.1 and exists in both versions and probably all in between.

I have included sample code below.

I am pretty sure this is a SwiftUI bug, but was wondering if anyone has a workaround for it. I need the edgesIgnoringSafeArea(.bottom) code to draw the TabBar, and for the ProfileView() to extend to the bottom of the screen when I hide my custom tab bar.

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() and ResourcesView() are just copies of ProfileView() that do their own thing.

When you run it you will see a button, push the button and a hidden Navigation Stack View pushes on the QuestionnaireView, this view contains a VStack with two text fields, neither of which you will be able to see due to this issue. Understandably the top one is behind the notch, but the bottom one is off the bottom of the screen. In my real project this issue is rarely seen at runtime, but switching between dark mode and light mode shows it. In the above code there is no need to switch appearances.

EDIT: FB7677794 for anyone interested, have not received any updates from Apple since lodging it 3 weeks ago.

EDIT2: Added some more code to MainTabBar

Update: This is fixed in Xcode 12 Beta 2

Upvotes: 2

Views: 3500

Answers (2)

Nilay
Nilay

Reputation: 335

After reading the updated question I have made some changes and tried to make a small demo. In this, I am using the same approach as before, put NavigationView in your main tab view and with this you don't have to hide and show every time you come or leave your main tab view.

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

Upvotes: 2

Asperi
Asperi

Reputation: 258117

It is due to undefined size for NavigationView. When you add your custom tab bar component, as in example below, that limits bottom area, the NavigationView will layout correctly.

Tested with Xcode 11.4 / iOS 13.4

demo

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

Upvotes: 2

Related Questions