SamuraiMelon
SamuraiMelon

Reputation: 377

SwiftUI - TabView Safe Area

I have some fullscreen views that I want to be able to swipe between in a TabView, and I know that I can expand the whole TabView container outside the Safe Area using .ignoresSafeArea() however the TabView page index also gets lowered, and I wish that to maintain its constraints inside the Safe Area. withinSafeArea

Tab Page Index higher within safe area, but background not filling screen

ignoresSafeArea()

Tab Page Index lower outside safe area, but background filling screen.

I need the Tab Page Index to be higher within the safe area, but the background (TabView View) to fill the screen.

Upvotes: 2

Views: 807

Answers (1)

Benzy Neez
Benzy Neez

Reputation: 21720

Just getting the background of the TabView to ignore the safe area insets is easy:

TabView {
    // ...
}
.tabViewStyle(.page)
.background(.blue)
  • or, if a closure is used to apply the background, apply .ignoresSafeArea() to the background content:
TabView {
    // ...
}
.tabViewStyle(.page)
.background {
    MyBackground().ignoresSafeArea()
}

However, you said that your views are full-screen, so I expect you want the TabView to respect the safe area insets (to keep the dots in the higher position) but have the views ignore the insets. I couldn't find a way to get this to work.

As a workaround, you can emulate the functionality of a TabView by using a ScrollView.

  • Use .scrollTargetBehavior(.paging) to allow the views to be switched by swiping, very similar to a TabView.
  • Use .scrollTargetLayout() in combination with .scrollPosition to track the selected view.
  • Your own custom page dots can then be shown as an overlay. These can be positioned and styled any way you like.

The example below has page dots that resemble the page indicators of a TabView:

struct ContentView: View {
    private let colors: [Color] = [.yellow, .orange, .green]
    @State private var selectedIndex: Int? = 0

    var body: some View {
        ScrollView(.horizontal) {
            HStack(spacing: 0) {
                ForEach(Array(colors.enumerated()), id: \.offset) { offset, color in
                    color
                        .containerRelativeFrame(.horizontal)
                }
            }
            .scrollTargetLayout()
        }
        .ignoresSafeArea()
        .scrollIndicators(.hidden)
        .scrollTargetBehavior(.paging)
        .scrollPosition(id: $selectedIndex)
        .overlay(alignment: .bottom) {
            PageDots(nPages: colors.count, currentIndex: selectedIndex ?? 0)
        }
    }
}

struct PageDots: View {
    let nPages: Int
    let currentIndex: Int

    var body: some View {
        HStack(spacing: 10) {
            ForEach(0..<nPages, id: \.self) { index in
                Circle()
                    .fill(.white)
                    .opacity(index == currentIndex ? 1.0 : 0.45)
                    .frame(width: 8)
            }
        }
        .animation(.easeInOut(duration: 0.2), value: currentIndex)
        .padding(.bottom, 20)
    }
}

Aniimation

Upvotes: 3

Related Questions