Skip to content

Instantly share code, notes, and snippets.

@dmeehan1968
Created October 16, 2021 15:49
Show Gist options
  • Save dmeehan1968/46577847f647af08befb009ea130d072 to your computer and use it in GitHub Desktop.
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 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