Section 7/181 menit
7. @Query Lanjutan: Section, Animation, dan Pagination
7. @Query Lanjutan: Section, Animation, dan Pagination
SectionedQuery (iOS 18+)
swift
import SwiftUI
import SwiftData
struct TaskListView: View {
// Sectioned query: task dikelompokkan berdasarkan priority
@Query(
filter: #Predicate<Task> { !$0.isCompleted },
sort: [SortDescriptor(\Task.priority), SortDescriptor(\Task.dueDate)]
)
private var tasks: [Task]
// Computed grouping dari tasks
private var tasksByPriority: [(String, [Task])] {
let grouped = Dictionary(grouping: tasks, by: \.priority)
return grouped.sorted { $0.key > $1.key } // high, medium, low
}
var body: some View {
List {
ForEach(tasksByPriority, id: \.0) { priority, tasks in
Section(priority.capitalized) {
ForEach(tasks) { task in
TaskRow(task: task)
}
}
}
}
}
}
FetchDescriptor untuk Pagination Manual
swift
// Pagination via FetchDescriptor — lebih kontrol daripada @Query
class TaskPaginationViewModel: ObservableObject {
@Published var tasks: [Task] = []
@Published var isLoading = false
private let pageSize = 20
private var currentPage = 0
private var hasMore = true
private let modelContext: ModelContext
init(modelContext: ModelContext) {
self.modelContext = modelContext
}
func loadNextPage() async {
guard !isLoading && hasMore else { return }
await MainActor.run { isLoading = true }
var descriptor = FetchDescriptor<Task>(
predicate: #Predicate { !$0.isCompleted },
sortBy: [SortDescriptor(\Task.createdAt, order: .reverse)]
)
descriptor.fetchLimit = pageSize
descriptor.fetchOffset = currentPage * pageSize
do {
let newTasks = try modelContext.fetch(descriptor)
await MainActor.run {
tasks.append(contentsOf: newTasks)
hasMore = newTasks.count == pageSize
currentPage += 1
isLoading = false
}
} catch {
await MainActor.run { isLoading = false }
}
}
func reset() {
tasks = []
currentPage = 0
hasMore = true
}
}
Fetch Count Tanpa Load Objects
swift
// Hitung jumlah record tanpa fetch semua data ke memori
func countPendingTasks(context: ModelContext) throws -> Int {
let descriptor = FetchDescriptor<Task>(
predicate: #Predicate { !$0.isCompleted }
)
return try context.fetchCount(descriptor)
}
// Cek apakah ada record yang memenuhi kondisi
func hasOverdueTasks(context: ModelContext) throws -> Bool {
var descriptor = FetchDescriptor<Task>(
predicate: #Predicate<Task> { task in
!task.isCompleted && task.dueDate < Date.now
}
)
descriptor.fetchLimit = 1 // Cukup cek satu saja
return try context.fetchCount(descriptor) > 0
}
@Query dengan Animation
swift
struct AnimatedTaskList: View {
@Query(
filter: #Predicate<Task> { !$0.isCompleted },
sort: \Task.createdAt,
animation: .spring(duration: 0.3) // Animasi saat data berubah
)
private var tasks: [Task]
var body: some View {
List {
ForEach(tasks) { task in
TaskRow(task: task)
.swipeActions {
Button("Selesai", systemImage: "checkmark") {
task.isCompleted = true
}
.tint(.green)
}
}
}
}
}