I'm having an issue with SwiftUI with a presented .sheet when a List content change. Let me explain in detail the situation.
I provided a sample example on Github that you can clone so you can easily reproduce the bug.
Context
I have only 2 views :
PlaylistCreationViewSearchView
Each of them have respectively their view models called PlaylistCreationViewModel and SearchViewModel.
The PlaylistCreationView has a Add Songs button witch will present the SearchView has a sheet. When the user touch a song in the search results, the SearchView sends a onAddSong completion handler that will notify the PlaylistCreationView that a song has been added.
Here a simple schema of the situation:
Here is also some screenshots of the views:
PlaylistCreationView
SearchView
Problem
When the user touch a song in the SearchView, the SearchView content is redrawn. I mean the view is recreated. The view appears has if it have been loaded for the first time again. Just like this:
To simplify the problem, here is another schema of the situation:
Here is what I can observe just after touching a song:
As you can see, the song has been added to the playlist correctly, but the SearchView is now blank because it has been recreated.
What I can't explain, is why the change of a list in the PlaylistCreationViewModel leads to redrawing the presented sheet.
How to reproduce the issue
I provided a sample example on Github that you can clone. To reproduce the issue:
- Clone the project and run it on any iPhone simulator.
- Touch "Add song".
- Type something on the search bar at the top of the search view.
- Select a song. Your search should have disappeared because the view has been rebuilded.
Code
PlaylistCreationViewModel
final class PlaylistCreationViewModel: ObservableObject {
@Published var sheetType: SheetType?
@Published var songs: [String] = []
}
extension PlaylistCreationViewModel {
enum SheetType: String, Identifiable {
case search
var id: String {
rawValue
}
}
}
PlaylistCreationView
struct PlaylistCreationView: View {
@ObservedObject var viewModel: PlaylistCreationViewModel
var body: some View {
NavigationView {
List {
ForEach(viewModel.songs, id: \.self) { song in
Text(song)
}
Button("Add Song") {
viewModel.sheetType = .search
}
.foregroundColor(.accentColor)
}
.navigationTitle("Playlist")
}
.sheet(item: $viewModel.sheetType) { _ in
sheetView
}
}
private var sheetView: some View {
let addingMode: SearchViewModel.AddingMode = .playlist { addedSong in
// This line leads SwiftUI to rebuild the SearchView
viewModel.songs.append(addedSong)
}
let sheetViewModel: SearchViewModel = SearchViewModel(addingMode: addingMode)
return NavigationView {
SearchView(viewModel: sheetViewModel)
.navigationTitle("Search")
}
}
}
SearchViewModel
final class SearchViewModel: ObservableObject {
@Published var search: String = ""
let songs: [String] = ["Within", "One more time", "Veridis quo"]
let addingMode: AddingMode
init(addingMode: AddingMode) {
self.addingMode = addingMode
}
}
extension SearchViewModel {
enum AddingMode {
case playlist(onAddSong: (_ song: String) -> Void)
}
}
SearchView
struct SearchView: View {
@ObservedObject var viewModel: SearchViewModel
var body: some View {
VStack {
TextField("Search", text: $viewModel.search)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
List(viewModel.songs, id: \.self) { song in
Button(song) {
switch viewModel.addingMode {
case .playlist(onAddSong: let onAddSong):
onAddSong(song)
}
}
}
}
}
}
I'm pretty sure it is some SwiftUI stuff that I'm missing. But I can't get it. I didn't encountered such a case before. I couldn't find anything on the web.
Any help or advice would be really appreciated. Thanks in advance to anyone who'll take the time to read and understand the problem. I hope I've described the problem well enough.





