I have been building an audio player application and have a tab in my app that queries the Song model (songs added by the user). The issue is that whenever the UI changes, swiftdata fetches the entire model multiple times; this would be fine if I had less than 50 Song objects but as the database grows, fetches put more strain on the main thread even when users perform simple action. I do not know how to fetch in a more efficient way whenever the UI changes.
this is a snippet from my code, whenever I select the ellipsis on the song from the foreach loop, a sheet comes up.
struct FrPlayerApp: App {
@AppStorage("isDarkMode") private var isDarkMode = false
@StateObject private var timerManager = SleepTimerManager()
var body: some Scene {
WindowGroup {
TabControl()
.preferredColorScheme(isDarkMode ? .dark: .light)
}
.environmentObject(timerManager)
.modelContainer(
for: [Song.self, Library.self, Album.self], isAutosaveEnabled: false)
}
struct LibraryView: View {
@Environment(.modelContext) private var context
@Query(sort: \Song.title, order: .forward) var songs: [Song]
@State private var isPresentingUser: Song? = nil
var body: some View {
ForEach(songs, id: .id) { song in
VStack {
HStack(spacing: 5) {
HStack {
Text(song.title)
.frame(height: 40)
.lineLimit(2)
.multilineTextAlignment(.leading)
.font(.subheadline)
Spacer()
}
// ... (other code)
HStack {
Image(systemName: "ellipsis")
}
.contentShape(Rectangle())
.frame(width: 50, height: 50)
.onTapGesture {
isPresentingUser = song
}
}
.padding(.horizontal)
.frame(height: 60)
}
Divider()
}
.sheet(item: $isPresentingUser) { song in
librarySongSheet(song: song)
.presentationDetents([.height(CGFloat(310))])
}
}
}
at 23 seconds I clicked on the ellipsis to open up the sheet and all of the objects in the database got fetched multiple times, this happens whenever the UI changes.
One option is to perform the fetching in a function and decide yourself when that function should be called to update the UI.
Change the songs
array first
@State private var songs = [Song]()
And you need access to a ModelContext
@Environment(\.modelContext) private var modelContext
Add a function
private func loadSongs() {
let fetchDescriptor = FetchDescriptor<Song>(sortBy: [SortDescriptor(\Song.title)])
do {
songs = try modelContext.fetch(fetchDescriptor)
} catch {
// Error handling here or make the function throw
}
}
And then use some relevant modifier to call the function
//... view code
}.onAppear(loadSongs)