Skip to content

Commit 6dda30d

Browse files
Hoang Phamclaude
andcommitted
Add configurable initial page for Pokemon infinite list
- Add initialOffset state variable to control starting position - Include offset in query key for proper cache separation - Add toolbar button showing current starting Pokemon number - Create OffsetPickerView with preset buttons and custom input - Support Pokemon numbers 1-1000 with proper offset conversion - Implement automatic refresh when initial page changes - Add validation and range limiting for API compatibility Features: - Quick preset buttons for common starting points (1, 51, 101, etc.) - Custom text field for any Pokemon number - Visual feedback for selected preset - Proper 0-based to 1-based number conversion - Modal sheet presentation with navigation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent dc309fc commit 6dda30d

1 file changed

Lines changed: 129 additions & 5 deletions

File tree

Example/swiftui-query-demo/ContentView.swift

Lines changed: 129 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,26 +115,30 @@ struct DemoButton: View {
115115

116116
struct PokemonListView: View {
117117
@State private var showDevTools = false
118+
@State private var initialOffset = 0
119+
@State private var showOffsetPicker = false
118120

119121
var body: some View {
120122
WithPerceptionTracking {
121123
UseInfiniteQuery(
122-
queryKey: "pokemon-infinite-list",
124+
queryKey: "pokemon-infinite-list-\(initialOffset)", // Include offset in key for cache separation
123125
queryFn: { _, pageParam in
124-
try await PokemonAPI.fetchPokemonPage(offset: pageParam ?? 0)
126+
let offset = pageParam ?? initialOffset
127+
return try await PokemonAPI.fetchPokemonPage(offset: offset)
125128
},
126129
getNextPageParam: { pages in
127-
// Calculate next offset based on current pages
130+
// Calculate next offset based on current pages and initial offset
128131
let currentTotal = pages.reduce(0) { total, page in total + page.results.count }
132+
let nextOffset = initialOffset + currentTotal
129133
let lastPage = pages.last
130134

131135
// If we have next URL or haven't reached the total count, continue pagination
132136
if let lastPage, lastPage.next != nil {
133-
return currentTotal
137+
return nextOffset
134138
}
135139
return nil // No more pages
136140
},
137-
initialPageParam: 0,
141+
initialPageParam: initialOffset,
138142
staleTime: 5 * 60 // 5 minutes before considered stale
139143
) { result in
140144
if result.isLoading, result.data?.pages.isEmpty != false {
@@ -233,6 +237,14 @@ struct PokemonListView: View {
233237
}
234238
.navigationTitle("Pokemon")
235239
.toolbar {
240+
ToolbarItem(placement: .navigationBarLeading) {
241+
Button("Start at #\(initialOffset + 1)") {
242+
showOffsetPicker = true
243+
}
244+
.font(.caption)
245+
.buttonStyle(.bordered)
246+
}
247+
236248
ToolbarItem(placement: .navigationBarTrailing) {
237249
Button("🛠️") {
238250
showDevTools = true
@@ -242,6 +254,9 @@ struct PokemonListView: View {
242254
.sheet(isPresented: $showDevTools) {
243255
DevToolsView()
244256
}
257+
.sheet(isPresented: $showOffsetPicker) {
258+
OffsetPickerView(initialOffset: $initialOffset)
259+
}
245260
}
246261
}
247262
}
@@ -798,3 +813,112 @@ struct ErrorView: View {
798813
}
799814
}
800815
}
816+
817+
// MARK: - Offset Picker View
818+
819+
struct OffsetPickerView: View {
820+
@Binding var initialOffset: Int
821+
@Environment(\.dismiss) private var dismiss
822+
@State private var selectedOffset: Int
823+
824+
init(initialOffset: Binding<Int>) {
825+
self._initialOffset = initialOffset
826+
self._selectedOffset = State(initialValue: initialOffset.wrappedValue)
827+
}
828+
829+
var body: some View {
830+
NavigationView {
831+
VStack(spacing: 24) {
832+
VStack(spacing: 16) {
833+
Text("🎯")
834+
.font(.system(size: 50))
835+
836+
Text("Choose Starting Pokemon")
837+
.font(.title2)
838+
.fontWeight(.bold)
839+
840+
Text(
841+
"Select which Pokemon number to start the list from. This demonstrates how infinite queries can begin from any offset."
842+
)
843+
.font(.subheadline)
844+
.foregroundColor(.secondary)
845+
.multilineTextAlignment(.center)
846+
.padding(.horizontal)
847+
}
848+
849+
VStack(spacing: 20) {
850+
// Quick preset buttons
851+
Text("Quick Presets")
852+
.font(.headline)
853+
.frame(maxWidth: .infinity, alignment: .leading)
854+
855+
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 12), count: 3), spacing: 12) {
856+
ForEach([0, 50, 100, 150, 200, 250, 300, 350, 400], id: \.self) { offset in
857+
Button("Pokemon #\(offset + 1)") {
858+
selectedOffset = offset
859+
}
860+
.buttonStyle(.bordered)
861+
.foregroundColor(selectedOffset == offset ? .blue : .primary)
862+
.fontWeight(selectedOffset == offset ? .semibold : .regular)
863+
}
864+
}
865+
866+
Divider()
867+
868+
// Custom offset input
869+
VStack(alignment: .leading, spacing: 12) {
870+
Text("Custom Pokemon Number")
871+
.font(.headline)
872+
873+
HStack {
874+
Text("Start at Pokemon #")
875+
TextField("1", value: Binding(
876+
get: { selectedOffset + 1 }, // Convert offset to Pokemon number
877+
set: { selectedOffset = max(0, $0 - 1) } // Convert Pokemon number to offset
878+
), format: .number)
879+
.textFieldStyle(.roundedBorder)
880+
.keyboardType(.numberPad)
881+
.frame(width: 80)
882+
}
883+
884+
Text("Enter a number between 1 and 1000")
885+
.font(.caption)
886+
.foregroundColor(.secondary)
887+
}
888+
.frame(maxWidth: .infinity, alignment: .leading)
889+
}
890+
891+
Spacer()
892+
893+
VStack(spacing: 12) {
894+
Button("Start from Pokemon #\(max(1, selectedOffset + 1))") {
895+
// Ensure valid range (0-999 for API compatibility)
896+
let validOffset = max(0, min(selectedOffset, 999))
897+
initialOffset = validOffset
898+
dismiss()
899+
}
900+
.buttonStyle(.borderedProminent)
901+
.disabled(selectedOffset < 0)
902+
903+
Button("Cancel") {
904+
dismiss()
905+
}
906+
.buttonStyle(.bordered)
907+
}
908+
}
909+
.padding()
910+
.navigationTitle("Starting Pokemon")
911+
.navigationBarTitleDisplayMode(.inline)
912+
.toolbar {
913+
ToolbarItem(placement: .navigationBarTrailing) {
914+
Button("Done") {
915+
let validOffset = max(0, min(selectedOffset, 999))
916+
initialOffset = validOffset
917+
dismiss()
918+
}
919+
}
920+
}
921+
}
922+
.presentationDetents([.medium, .large])
923+
}
924+
}

0 commit comments

Comments
 (0)