Skip to content

Instantly share code, notes, and snippets.

@cjnevin
Last active May 4, 2023 21:17
Show Gist options
  • Save cjnevin/6ee641a4586c44f769ea9a19a6c33611 to your computer and use it in GitHub Desktop.
Save cjnevin/6ee641a4586c44f769ea9a19a6c33611 to your computer and use it in GitHub Desktop.
import SwiftUI
struct Tab: Equatable, Identifiable {
var id: UUID = UUID()
var color: Color
}
struct OffsetKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
extension View {
func offsetPreference() -> some View {
self.overlay {
GeometryReader { proxy in
Color.clear
.preference(key: OffsetKey.self, value: proxy.frame(in: .global).minX)
}
}
}
}
struct CardView: View {
let color: Color
var body: some View {
HStack {
Spacer()
RoundedRectangle(cornerRadius: 25, style: .continuous)
.fill(color)
.frame(width: 300)
Spacer()
}
}
}
struct InfiniteTabView: View {
@Binding var tabs: [Tab]
@State private var loopedTabs: [Tab] = []
@Binding var currentIndex: Int
@State private var offset: CGFloat = 0
var body: some View {
GeometryReader { reader in
let size = reader.size
TabView(selection: $currentIndex) {
ForEach(loopedTabs) { tab in
CardView(color: tab.color)
.frame(maxWidth: .infinity, maxHeight: size.height)
.tag(getIndex(of: tab))
.offsetPreference()
.onPreferenceChange(OffsetKey.self) { newOffset in
offset = newOffset
}
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
}
.frame(height: 400)
.onChange(of: offset, perform: { newValue in
if currentIndex == 0 && offset == 0 {
currentIndex = loopedTabs.count - 2
}
if currentIndex == loopedTabs.count - 1 && offset == 0 {
currentIndex = 1
}
})
.onAppear {
reloadTabs(setIndex: true)
}
.onChange(of: tabs, perform: { newValue in
reloadTabs(setIndex: false)
})
}
func reloadTabs(setIndex: Bool) {
loopedTabs = tabs
if var firstPage = tabs.first, var lastPage = tabs.last {
firstPage.id = .init()
lastPage.id = .init()
if setIndex {
currentIndex = 1
}
loopedTabs.append(firstPage)
loopedTabs.insert(lastPage, at: 0)
}
}
func getIndex(of tab: Tab) -> Int {
loopedTabs.firstIndex(of: tab) ?? 0
}
}
struct ContentView: View {
@State var tabs: [Tab] = [
.init(color: .red),
.init(color: .blue),
.init(color: .green),
.init(color: .orange),
.init(color: .yellow),
.init(color: .purple)
]
@State var index: Int = 0
var body: some View {
VStack {
Button {
tabs.append(.init(color: .cyan))
} label: {
Text("Add")
}
InfiniteTabView(tabs: $tabs, currentIndex: $index)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment