0
import SwiftUI
struct OnboardingView: View {        
    @ObservedObject var viewModel: OnboardingViewModel        
    var body: some View {        
        TabView(selection: $viewModel.currentPage) {           
            ForEach(OnboardingPages.allCases, id: \.rawValue) { page in                
                getPageView(for: page)                    
                    .tag(page.rawValue)            
                }        
            }        
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))            
}        

@ViewBuilder    
private func getPageView(for page: OnboardingPages) -> some View {        
    VStack(spacing: 32) {                       
        Spacer()                        
        VStack(alignment: .center, spacing: 8) {                
            Text(page.title)                    
                .font(.urb(.semiBold, size: 32))                    
                .foregroundStyle(.white)                                
            Text(page.description)                    
                .font(.urb(.regular, size: 16))                    
                .foregroundStyle(.white)                    
                .multilineTextAlignment(.center)            
        }                        
        
        continueButton                        
           
        dots        
    }        
    .padding(.horizontal, 16)        
    .padding(.bottom, 35)        
    .ignoresSafeArea()        
    .background(            
        Image(page.image)                
            .resizable()                
            .scaledToFill()                
            .ignoresSafeArea()        
    )    
}

The Navigation Stack:

struct RootView: View {

    @StateObject private var router = Router()
    
    var body: some View {
        NavigationStack(path: $router.path) {
            Color.clear
                .navigationDestination(for: Route.self) { route in
                    switch route {
                    
                    case .launch:
                        LaunchScreen()
                            .onAppear { router.decideInitialFlow() }
                            .navigationBarBackButtonHidden(true)
                    
                    case .onboarding:
                        OnboardingView(viewModel: OnboardingViewModel(finish: router.finishOnboarding))
                            .navigationBarBackButtonHidden(true)
                    }
                }
        }
    }
}

The problem is:

  1. Even without NavigationStack, ignoresSafeArea stretches TabView out of safe area
  2. If ignoresSafeArea modifier is used with TabView, then content is go beyond safe area
  3. If ignoresSafeAre modifier is not used with TabView, then the image does not cover full screen
VStack and no safe area: Vstack and safe area:
VStack and no safe area Vstack and safe area
2
  • The first image looks alright to me. What's wrong with it? Commented Nov 18 at 12:52
  • Hello! The content is beyond safe area on the 1st image. Commented Nov 19 at 9:49

1 Answer 1

-1

If I understand correctly, you want the background image to ignore the safe area insets, but the page content should respect the insets.

As you already noted, the background only extends to the screen edges if you apply .ignoresSafeArea() to the TabView itself:

TabView(selection: $viewModel.currentPage) {
    // ... content as before
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
.ignoresSafeArea() // 👈 here

Then, to keep the page content inside the safe area, you could try the following approach:

  • Use Color.clear as the base view.
  • Apply the image as background to this base. The modifier .ignoreSafeArea() is not needed here.
  • Apply .ignoresSafeArea() to this combination.
  • Apply the regular content as an .overlay. Important: the .overlay modifier must follow after .ignoresSafeArea():
@ViewBuilder
private func getPageView(for page: OnboardingPages) -> some View {
    Color.clear
        .background {
            Image(page.image)
                .resizable()
                .scaledToFill()
                // .ignoresSafeArea() // 👈 not needed
        }
        .ignoresSafeArea()
        .overlay {
            VStack(spacing: 32) {
                // ... content as before
            }
            .padding(.horizontal, 16)
            .padding(.bottom, 35)
        }
}

Screenshot

If you want less space at the bottom, just change the padding being applied to the VStack.

There would in fact be other ways to have the background extend to the screen edges. For example, it can be applied as background to the VStack (the modifier .ignoresSafeArea is then needed again when doing it this way). However, in my tests, the background is only correctly centered when it is done as shown in the code above.


Ps. The title refers to a NavigationStack, but there was no stack in your example code. You should just be aware that a TabView is not allowed to be a child of a NavigationStack - Apple does not support this configuration and it doesn't always work as expected. But a NavigationStack can be a child of a TabView.

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

5 Comments

Ps. See this comment for some background to the downvote.
Hello, thank you very much. I edited my question and added how I use navigation stack. Now, I am a bit confused, since I did not know that TabView cannot be a child of NavigationStack. Is there a way to solve the issue? I am using TabView to have animation of sliding from right to left.
For the case you've shown, the NavigationStack could perhaps be replaced by a simple ZStack. See this answer for an example.
If I will replace it, the navigation will have no meaning. I have 6 screens to be shown and the navigation between them is navigation stack. Can you provide some other options, if you know?
The child views (LaunchScreen and OnboardingView) can have their own NavigationStack, for navigation within the child (if needed). Otherwise, perhaps you can elaborate on what kind of navigation routes are possible. But not here - please create a new question for that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.