Created
October 16, 2021 15:49
-
-
Save dmeehan1968/46577847f647af08befb009ea130d072 to your computer and use it in GitHub Desktop.
How to handle SwiftUI sheet(item:onDismiss:content) unwrapping of optional whilst allowing binding to be passed to sheet view
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// This demonstrates how to use sheet(item:...) when the requirement is to pass a binding to the | |
// view presented by the sheet. Also relevant to fullCoverSheet(item:...) | |
// | |
// What we want the sheet to do is provide an editable form that can be cancelled, so partial changes | |
// are not committed to the ancestors view state. We only want the state changed IF the form is confirmed (save action) | |
// | |
import SwiftUI | |
struct Movie: Identifiable { | |
var id = UUID() | |
var title: String | |
} | |
struct MovieEdit: View { | |
@Binding var movie: Movie | |
var onCancel: () -> Void = {} | |
var onSave: () -> Void = {} | |
var body: some View { | |
NavigationView { | |
TextField("Title", text: .init(get: { movie.title }, set: { movie.title = $0 })) | |
.navigationBarTitle("Details", displayMode: .inline) | |
.toolbar { | |
ToolbarItem(placement: .cancellationAction) { | |
Button("Cancel", action: onCancel) | |
} | |
ToolbarItem(placement: .confirmationAction) { | |
Button("Done", action: onSave) | |
} | |
} | |
} | |
} | |
} | |
struct TestView: View { | |
// Here we store the list of items | |
@Binding var movies: [Movie] | |
// Note: usually this gets described as @State var X: Item?, but in practice this only works if | |
// the sheet view does not require a binding, which is contrary to how Apple's HIG describe the | |
// purpose of sheets as modal views for the purpose of editing | |
@State var selectedMovie: Binding<Movie>? | |
var body: some View { | |
List($movies) { $movie in | |
HStack { | |
Text(movie.title) | |
Spacer() | |
Text("Edit") | |
.foregroundColor(.accentColor) | |
.onTapGesture { | |
selectedMovie = $movie | |
} | |
} | |
} | |
.sheet(item: $selectedMovie) { movie in // movie: Binding<Movie> (optional has been unwrapped) | |
var editMovie = movie.wrappedValue // take a mutable copy so we can cancel editing without altering state | |
// pass a custom binding referencing (and updating) the mutable copy | |
MovieEdit(movie: .init(get: { editMovie }, set: { editMovie = $0 }), | |
onCancel: { | |
selectedMovie = nil // dismiss the sheet by reverting the binding to nil | |
}, | |
onSave: { | |
// when save is chosen, update the view state with the revised values from the form | |
movie.wrappedValue = editMovie | |
selectedMovie = nil // dismiss the sheet | |
}) | |
} | |
} | |
} | |
struct TestView_Previews: PreviewProvider { | |
@State static var movies: [Movie] = [ | |
Movie(title: "War of the Worlds"), | |
Movie(title: "Blazing Saddles"), | |
Movie(title: "Gone with the Wind") | |
] | |
static var previews: some View { | |
TestView(movies: $movies) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment