swiftui

TabView with colored gaps between views


Suppose I have 3 views in a page-style TabView. I want there to be gaps between the views that are filled with a specific color that’s different than the TabView’s background color.

For example, here are some views with yellow gaps between them: views & gaps

The gaps should not be visible when the TabView is at rest:

single view

They should only appear when the TabView is being scrolled:

scrolling view

Here’s some sample code:

TabView {
    Color.red
        .padding(.horizontal, 20)
        .background(.yellow)
    Color.blue
        .padding(.horizontal, 20)
        .background(.yellow)
    Color.green
        .padding(.horizontal, 20)
        .background(.yellow)
}
.tabViewStyle(.page(indexDisplayMode: .never))
.indexViewStyle(.page(backgroundDisplayMode: .always))
.frame(width: 150, height: 150)

The padding/background color creates colored gaps, but the gaps are visible when the TabView is at rest:

view with padding

Is there any way to keep the colored gaps between the views (or have them be drawn by the views), but have the TabView exactly fit the red/blue/green portion of the views?


Solution

  • This effect can be achieved by adding negative horizontal padding to the background behind each view:

    TabView {
        Color.red
            .background {
                Color.yellow
                    .padding(.horizontal, -20)
            }
        Color.blue
            .background {
                Color.yellow
                    .padding(.horizontal, -20)
            }
        Color.green
            .background {
                Color.yellow
                    .padding(.horizontal, -20)
            }
    }
    .tabViewStyle(.page(indexDisplayMode: .never))
    .indexViewStyle(.page(backgroundDisplayMode: .always))
    .frame(width: 150, height: 150)
    .background(.yellow)
    

    Animation

    However, the yellow stripes do not always disappear as smoothly as shown in the gif above, particularly if the drag gesture only pulls in a smaller fraction of the next view.

    A more reliable approach might therefore be to use a ScrollView with view-aligned .scrollTargetBehavior. For example:

    ScrollView(.horizontal) {
        LazyHStack(spacing: 20) {
            Color.red
                .containerRelativeFrame(.horizontal)
            Color.blue
                .containerRelativeFrame(.horizontal)
            Color.green
                .containerRelativeFrame(.horizontal)
        }
        .scrollTargetLayout()
        .background(.yellow)
    }
    .scrollTargetBehavior(.viewAligned)
    .scrollIndicators(.hidden)
    .frame(width: 150, height: 150)
    .background(.yellow)