Here is a simple implementation. You can use the struct with the AnyView array or use the logic directly in your own implementation.
struct ContentView: View {
@State private var selected = 4
var body: some View {
// the trailing closure takes an Array of AnyView type erased views
TeasingTabView(selectedTab: $selected, spacing: 20) {
[
AnyView(TabContentView(title: "First", color: .yellow)),
AnyView(TabContentView(title: "Second", color: .orange)),
AnyView(TabContentView(title: "Fourth", color: .green)),
AnyView(TabContentView(title: "Fifth", color: .blue)),
AnyView(
Image(systemName: "lizard")
.resizable().scaledToFit()
.padding()
.frame(maxHeight: .infinity)
.border(.red)
)
]
}
}
}
struct TeasingTabView: View {
@Binding var selectedTab: Int
let spacing: CGFloat
let views: () -> [AnyView]
@State private var offset = CGFloat.zero
var viewCount: Int { views().count }
var body: some View {
VStack(spacing: spacing) {
GeometryReader { geo in
let width = geo.size.width * 0.7
LazyHStack(spacing: spacing) {
Color.clear
.frame(width: geo.size.width * 0.15 - spacing)
ForEach(0..<viewCount, id: \.self) { idx in
views()[idx]
.frame(width: width)
.padding(.vertical)
}
}
.offset(x: CGFloat(-selectedTab) * (width + spacing) + offset)
.animation(.easeOut, value: selectedTab)
.gesture(
DragGesture()
.onChanged { value in
offset = value.translation.width
}
.onEnded { value in
withAnimation(.easeOut) {
offset = value.predictedEndTranslation.width
selectedTab -= Int((offset / width).rounded())
selectedTab = max(0, min(selectedTab, viewCount-1))
offset = 0
}
}
)
}
//
HStack {
ForEach(0..<viewCount, id: \.self) { idx in
Circle().frame(width: 8)
.foregroundColor(idx == selectedTab ? .primary : .secondary.opacity(0.5))
.onTapGesture {
selectedTab = idx
}
}
}
}
}
}
struct TabContentView: View {
let title: String
let color: Color
var body: some View {
Text(title).font(.title)
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(color.opacity(0.4), ignoresSafeAreaEdges: .all)
.clipShape(RoundedRectangle(cornerRadius: 20))
}
}