How to delete multiple rows from List in SwiftUI? - list

I took an example from this question: How does one enable selections in SwiftUI's List and edited the code to be able to delete rows one by one. But I don't know how to delete multiple rows from list.
Could you help me, please?
var demoData = ["Phil Swanson", "Karen Gibbons", "Grant Kilman", "Wanda Green"]
struct ContentView : View {
#State var selectKeeper = Set<String>()
var body: some View {
NavigationView {
List(selection: $selectKeeper){
ForEach(demoData, id: \.self) { name in
Text(name)
}
.onDelete(perform: delete)
}
.navigationBarItems(trailing: EditButton())
.navigationBarTitle(Text("Selection Demo \(selectKeeper.count)"))
}
}
func delete(at offsets: IndexSet) {
demoData.remove(atOffsets: offsets)
}
}

solution from SwiftUI how to perform action when EditMode changes?
struct Item: Identifiable {
let id = UUID()
let title: String
static var i = 0
init() {
self.title = "\(Item.i)"
Item.i += 1
}
}
struct ContentView: View {
#State var editMode: EditMode = .inactive
#State var selection = Set<UUID>()
#State var items = [Item(), Item(), Item()]
var body: some View {
NavigationView {
List(selection: $selection) {
ForEach(items) { item in
Text(item.title)
}
}
.navigationBarTitle(Text("Demo"))
.navigationBarItems(
leading: editButton,
trailing: addDelButton
)
.environment(\.editMode, self.$editMode)
}
}
private var editButton: some View {
Button(action: {
self.editMode.toggle()
self.selection = Set<UUID>()
}) {
Text(self.editMode.title)
}
}
private var addDelButton: some View {
if editMode == .inactive {
return Button(action: addItem) {
Image(systemName: "plus")
}
} else {
return Button(action: deleteItems) {
Image(systemName: "trash")
}
}
}
private func addItem() {
items.append(Item())
}
private func deleteItems() {
for id in selection {
if let index = items.lastIndex(where: { $0.id == id }) {
items.remove(at: index)
}
}
selection = Set<UUID>()
}
}
extension EditMode {
var title: String {
self == .active ? "Done" : "Edit"
}
mutating func toggle() {
self = self == .active ? .inactive : .active
}
}

Related

#EnvironmentObject bug in SwiftUI

When I press the Move button in the contextMenu, I change the isCopied and setOriginPath variables in the EnvironmentObject. When this change is made, the List view is cleared and I can't see anything on the screen. I don't have any problems when I don't use EnvironmentObject.
ContextMenu:
.contextMenu {
Button {
safeFileVM.hideSelectedFile(fileName: currentFile.fileName)
safeFileVM.takeArrayOfItems()
} label: {
HStack {
Text(!currentFile.isLock ? "Hide" : "Show")
Image(systemName: currentFile.isLock ? "eye" : "eye.slash")
}
}
Button {
safeFileClipboard.setOriginPath = URL(fileURLWithPath: currentFile.localPath)
safeFileClipboard.isCopied = true
} label: {
HStack {
Text("Move")
Image(systemName: "arrow.up.doc")
}
}
}
View:
struct DetailObjectView: View {
#ObservedObject var safeFileVM: SafeFileViewModel = SafeFileViewModel()
#EnvironmentObject var safeFileClipboard: SafeFileClipBoard
var currentFile: MyFile
var currentLocation = ""
var body: some View {
VStack {
.....
}
.contextMenu {
Button {
safeFileVM.hideSelectedFile(fileName: currentFile.fileName)
safeFileVM.takeArrayOfItems()
} label: {
HStack {
Text(!currentFile.isLock ? "Hide" : "Show")
Image(systemName: currentFile.isLock ? "eye" : "eye.slash")
}
}
Button {
safeFileClipboard.setOriginPath = URL(fileURLWithPath: currentFile.localPath)
safeFileClipboard.isCopied = true
} label: {
HStack {
Text("Move")
Image(systemName: "arrow.up.doc")
}
}
}
}
}
In the mini project below, when the EnvironmentObject value changes, navigation goes to the beginning. Why ? How can I fix this ?
Example Project:
Main:
#main
struct EnvironmentTestApp: App {
#StateObject var fooConfig: FooConfig = FooConfig()
var body: some Scene {
WindowGroup {
NavigationView {
ContentView()
.environmentObject(fooConfig)
}
}
}
}
ContentView:
struct ContentView: View {
#EnvironmentObject var fooConfig: FooConfig
private let numbers: [Number] = [.init(item: "1"), .init(item: "2"), .init(item: "3")]
var body: some View {
List {
ForEach(numbers, id: \.id) { item in
DetailView(itemNumber: item.item)
}
}
}
}
struct Number: Identifiable {
var id = UUID()
var item: String
}
DetailView:
struct DetailView: View {
#EnvironmentObject var fooConfig: FooConfig
var itemNumber: String = ""
var body: some View {
NavigationLink(destination: ContentView().environmentObject(fooConfig)) {
Text("\(itemNumber) - \(fooConfig.fooBool == true ? "On" : "Off")")
.environmentObject(fooConfig)
.contextMenu {
Button {
fooConfig.fooBool.toggle()
} label: {
HStack {
Text(fooConfig.fooBool != true ? "On" : "Off")
}
}
}
}
}
}
ObservableObject:
class FooConfig: ObservableObject {
#Published var fooBool: Bool = false
}
Move that from scene into ContentView, because scene is a bad place to update view hierarchy, it is better to do inside view hierarchy, so here
struct EnvironmentTestApp: App {
var body: some Scene {
WindowGroup {
ContentView() // only root view here !!
}
}
}
everything else is inside views, like
struct ContentView: View {
#StateObject private var foo = FooConfig()
var body: some View {
NavigationView {
MainView()
.environmentObject(foo) // << here !!
}
}
}
struct MainView: View {
#EnvironmentObject var fooConfig: FooConfig
private let numbers: [Number] = [.init(item: "1"), .init(item: "2"), .init(item: "3")]
var body: some View {
List {
ForEach(numbers, id: \.id) { item in
DetailView(itemNumber: item.item)
}
}
}
}
and so on...
Tested with Xcode 14 / iOS 16

SwiftUI List single selectable item

I'm trying to create a List and allow only one item to be selected at a time. How would I do so in a ForEach loop? I can select multiple items just fine, but the end goal is to have only one checkmark in the selected item in the List. It may not even be the proper way to handle what I'm attempting.
struct ContentView: View {
var body: some View {
NavigationView {
List((1 ..< 4).indices, id: \.self) { index in
CheckmarkView(index: index)
.padding(.all, 3)
}
.listStyle(PlainListStyle())
.navigationBarTitleDisplayMode(.inline)
//.environment(\.editMode, .constant(.active))
}
}
}
struct CheckmarkView: View {
let index: Int
#State var check: Bool = false
var body: some View {
Button(action: {
check.toggle()
}) {
HStack {
Image("Image-\(index)")
.resizable()
.frame(width: 70, height: 70)
.cornerRadius(13.5)
Text("Example-\(index)")
Spacer()
if check {
Image(systemName: "checkmark")
.resizable()
.frame(width: 12, height: 12)
}
}
}
}
}
You'll need something to store all of the states instead of storing it per-checkmark view, because of the requirement to just have one thing checked at a time. I made a little example where the logic is handled in an ObservableObject and passed to the checkmark views through a custom Binding that handles checking/unchecking states:
struct CheckmarkModel {
var id = UUID()
var state = false
}
class StateManager : ObservableObject {
#Published var checkmarks = [CheckmarkModel(), CheckmarkModel(), CheckmarkModel(), CheckmarkModel()]
func singularBinding(forIndex index: Int) -> Binding<Bool> {
Binding<Bool> { () -> Bool in
self.checkmarks[index].state
} set: { (newValue) in
self.checkmarks = self.checkmarks.enumerated().map { itemIndex, item in
var itemCopy = item
if index == itemIndex {
itemCopy.state = newValue
} else {
//not the same index
if newValue {
itemCopy.state = false
}
}
return itemCopy
}
}
}
}
struct ContentView: View {
#ObservedObject var state = StateManager()
var body: some View {
NavigationView {
List(Array(state.checkmarks.enumerated()), id: \.1.id) { (index, item) in //<-- here
CheckmarkView(index: index + 1, check: state.singularBinding(forIndex: index))
.padding(.all, 3)
}
.listStyle(PlainListStyle())
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct CheckmarkView: View {
let index: Int
#Binding var check: Bool //<-- Here
var body: some View {
Button(action: {
check.toggle()
}) {
HStack {
Image("Image-\(index)")
.resizable()
.frame(width: 70, height: 70)
.cornerRadius(13.5)
Text("Example-\(index)")
Spacer()
if check {
Image(systemName: "checkmark")
.resizable()
.frame(width: 12, height: 12)
}
}
}
}
}
What's happening:
There's a CheckmarkModel that has an ID for each checkbox, and the state of that box
StateManager keeps an array of those models. It also has a custom binding for each index of the array. For the getter, it just returns the state of the model at that index. For the setter, it makes a new copy of the checkbox array. Any time a checkbox is set, it unchecks all of the other boxes. I also kept your original behavior of allowing nothing to be checked
The List now gets an enumeration of the state.checkmarks -- using enumerated lets me keep your previous behavior of being able to pass an index number to the checkbox view
Inside the ForEach, the custom binding from before is created and passed to the subview
In the subview, instead of using #State, #Binding is used (this is what the custom Binding is passed to)
List {
ForEach(0 ..< RemindTimeType.allCases.count) {
index in CheckmarkView(title:getListTitle(index), index: index, markIndex: $markIndex)
.padding(.all, 3)
}.listRowBackground(Color.clear)
}
struct CheckmarkView: View {
let title: String
let index: Int
#Binding var markIndex: Int
var body: some View {
Button(action: {
markIndex = index
}) {
HStack {
Text(title)
.foregroundColor(Color.white)
.font(.custom(FontEnum.Regular.fontName, size: 14))
Spacer()
if index == markIndex {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(hex: 0xe6c27c))
}
}
}
}
}
We can benefit from binding collections of Swift 5.5.
import SwiftUI
struct CheckmarkModel: Identifiable, Hashable {
var id = UUID()
var state = false
}
class StateManager : ObservableObject {
#Published var checkmarks = [CheckmarkModel(), CheckmarkModel(), CheckmarkModel(), CheckmarkModel()]
}
struct SingleSelectionList<Content: View>: View {
#Binding var items: [CheckmarkModel]
#Binding var selectedItem: CheckmarkModel?
var rowContent: (CheckmarkModel) -> Content
#State var previouslySelectedItemNdx: Int?
var body: some View {
List(Array($items.enumerated()), id: \.1.id) { (ndx, $item) in
rowContent(item)
.modifier(CheckmarkModifier(checked: item.id == self.selectedItem?.id))
.contentShape(Rectangle())
.onTapGesture {
if let prevIndex = previouslySelectedItemNdx {
items[prevIndex].state = false
}
self.selectedItem = item
item.state = true
previouslySelectedItemNdx = ndx
}
}
}
}
struct CheckmarkModifier: ViewModifier {
var checked: Bool = false
func body(content: Content) -> some View {
Group {
if checked {
ZStack(alignment: .trailing) {
content
Image(systemName: "checkmark")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(.green)
.shadow(radius: 1)
}
} else {
content
}
}
}
}
struct ContentView: View {
#ObservedObject var state = StateManager()
#State private var selectedItem: CheckmarkModel?
var body: some View {
VStack {
Text("Selected Item: \(selectedItem?.id.description ?? "Select one")")
Divider()
SingleSelectionList(items: $state.checkmarks, selectedItem: $selectedItem) { item in
HStack {
Text(item.id.description + " " + item.state.description)
Spacer()
}
}
}
}
}
A bit simplified version
struct ContentView: View {
#ObservedObject var state = StateManager()
#State private var selection: CheckmarkModel.ID?
var body: some View {
List {
ForEach($state.checkmarks) { $item in
SelectionCell(item: $item, selectedItem: $selection)
.onTapGesture {
if let ndx = state.checkmarks.firstIndex(where: { $0.id == selection}) {
state.checkmarks[ndx].state = false
}
selection = item.id
item.state = true
}
}
}
.listStyle(.plain)
}
}
struct SelectionCell: View {
#Binding var item: CheckmarkModel
#Binding var selectedItem: CheckmarkModel.ID?
var body: some View {
HStack {
Text(item.id.description + " " + item.state.description)
Spacer()
if item.id == selectedItem {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
}
}
A version that uses internal List's selected mark and selection:
import SwiftUI
struct CheckmarkModel: Identifiable, Hashable {
var name: String
var state: Bool = false
var id = UUID()
}
class StateManager : ObservableObject {
#Published var checkmarks = [CheckmarkModel(name: "Name1"), CheckmarkModel(name: "Name2"), CheckmarkModel(name: "Name3"), CheckmarkModel(name: "Name4")]
}
struct ContentView: View {
#ObservedObject var state = StateManager()
#State private var selection: CheckmarkModel.ID?
#State private var selectedItems = [CheckmarkModel]()
var body: some View {
VStack {
Text("Items")
List($state.checkmarks, selection: $selection) { $item in
Text(item.name + " " + item.state.description)
}
.onChange(of: selection) { s in
for index in state.checkmarks.indices {
if state.checkmarks[index].state == true {
state.checkmarks[index].state = false
}
}
selectedItems = []
if let ndx = state.checkmarks.firstIndex(where: { $0.id == selection}) {
state.checkmarks[ndx].state = true
selectedItems = [state.checkmarks[ndx]]
print(selectedItems)
}
}
.environment(\.editMode, .constant(.active))
Divider()
List(selectedItems) {
Text($0.name + " " + $0.state.description)
}
}
Text("\(selectedItems.count) selections")
}
}

Refreshing a SwiftUI List

Ím trying to refresh this List whenever I click on a NavLink
NavigationView {
List(feed.items.indices, id:\.self) { i in
NavigationLink(destination: ListFeedItemDetail(idx: i).environmentObject(self.feed)) {
ListFeedItem(item: self.$feed.items[i])
}
}
}
The list is made out of an array inside an environment object.
The problem: It does only refresh when I switch to another tab or close the app
I had used a modal View before and it worked there. (I did it with .onAppear)
Any Ideas?
Example
Problem: When you tap on an item in the list and tap the toggle button the EnvironmentObject is changed but this changed is only reflected when I change the tab and change it back again
import SwiftUI
import Combine
struct TestView: View {
#State var showSheet: Bool = false
#EnvironmentObject var feed: TestObject
func addObjects() {
var strings = ["one","two","three","four","five","six"]
for s in strings {
var testItem = TestItem(text: s)
self.feed.items.append(testItem)
}
}
var body: some View {
TabView {
NavigationView {
List(feed.items.indices, id:\.self) { i in
NavigationLink(destination: detailView(feed: self._feed, i: i)) {
HStack {
Text(self.feed.items[i].text)
Text("(\(self.feed.items[i].read.description))")
}
}
}
}
.tabItem({ Text("Test") })
.tag(0)
Text("Blank")
.tabItem({ Text("Test") })
.tag(0)
}.onAppear {
self.addObjects()
}
}
}
struct detailView: View {
#EnvironmentObject var feed: TestObject
var i: Int
var body: some View {
VStack {
Text(feed.items[i].text)
Text(feed.items[i].read.description)
Button(action: { self.feed.items[self.i].isRead.toggle() }) {
Text("Toggle read")
}
}
}
}
final class TestItem: ObservableObject {
init(text: String) {
self.text = text
self.isRead = false
}
static func == (lhs: TestItem, rhs: TestItem) -> Bool {
lhs.text < rhs.text
}
var text: String
var isRead: Bool
let willChange = PassthroughSubject<TestItem, Never>()
var read: Bool {
set {
self.isRead = newValue
}
get {
self.isRead
}
}
}
class TestObject: ObservableObject {
var willChange = PassthroughSubject<TestObject, Never>()
#Published var items: [TestItem] = [] {
didSet {
willChange.send(self)
}
}
}
I had a similar problem, this is the hack I came up with.
In your "TestView" declare:
#State var needRefresh: Bool = false
Pass this to your "detailView" destination, such as:
NavigationLink(destination: detailView(feed: self._feed, i: i, needRefresh: self.$needRefresh)) {
HStack {
Text(self.feed.items[i].text)
Text("(\(self.feed.items[i].read.description))")
}.accentColor(self.needRefresh ? .white : .black)
}
Note ".accentColor(self.needRefresh ? .white : .black)" to force a refresh when "needRefresh"
is changed.
In your "detailView" destination add:
#Binding var needRefresh: Bool
Then in your "detailView" in your Button action, add:
self.needRefresh.toggle()

SwiftUI how to perform action when EditMode changes?

I'd like to perform an action when the EditMode changes.
Specifically, in edit mode, the user can select some items to delete. He normally presses the trash button afterwards. But he may also press Done. When he later presses Edit again, the items that were selected previously are still selected. I would like all items to be cleared.
struct ContentView: View {
#State var isEditMode: EditMode = .inactive
#State var selection = Set<UUID>()
var items = [Item(), Item(), Item(), Item(), Item()]
var body: some View {
NavigationView {
List(selection: $selection) {
ForEach(items) { item in
Text(item.title)
}
}
.navigationBarTitle(Text("Demo"))
.navigationBarItems(
leading: EditButton(),
trailing: addDelButton
)
.environment(\.editMode, self.$isEditMode)
}
}
private var addDelButton: some View {
if isEditMode == .inactive {
return Button(action: reset) {
Image(systemName: "plus")
}
} else {
return Button(action: reset) {
Image(systemName: "trash")
}
}
}
private func reset() {
selection = Set<UUID>()
}
}
Definition of Item:
struct Item: Identifiable {
let id = UUID()
let title: String
static var i = 0
init() {
self.title = "\(Item.i)"
Item.i += 1
}
}
UPDATED for iOS 15.
This solution catches 2 birds with one stone:
The entire view redraws itself when editMode is toggle
A specific action can be performed upon activation/inactivation of editMode
Hopes this helps someone else.
struct ContentView: View {
#State var editMode: EditMode = .inactive
#State var selection = Set<UUID>()
#State var items = [Item(), Item(), Item()]
var body: some View {
NavigationView {
List(selection: $selection) {
ForEach(items) { item in
Text(item.title)
}
}
.navigationTitle(Text("Demo"))
.environment(\.editMode, self.$editMode)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
editButton
}
ToolbarItem(placement: .navigationBarTrailing) {
addDelButton
}
}
}
}
private var editButton: some View {
Button(action: {
self.editMode.toggle()
self.selection = Set<UUID>()
}) {
Text(self.editMode.title)
}
}
private var addDelButton: some View {
if editMode == .inactive {
return Button(action: addItem) {
Image(systemName: "plus")
}
} else {
return Button(action: deleteItems) {
Image(systemName: "trash")
}
}
}
private func addItem() {
items.append(Item())
}
private func deleteItems() {
for id in selection {
if let index = items.lastIndex(where: { $0.id == id }) {
items.remove(at: index)
}
}
selection = Set<UUID>()
}
}
extension EditMode {
var title: String {
self == .active ? "Done" : "Edit"
}
mutating func toggle() {
self = self == .active ? .inactive : .active
}
}
I was trying forever, to clear List selections when the user exited editMode. For me, the cleanest way I've found to react to a change of editMode:
Make sure to reference the #Environment variable:
#Environment(\.editMode) var editMode
Add a computed property in the view to monitor the state:
private var isEditing: Bool {
if editMode?.wrappedValue.isEditing == true {
return true
}
return false
}
Then use the .onChange(of:perform:) method:
.onChange(of: self.isEditing) { value in
if value == false {
// do something
} else {
// something else
}
}
All together:
struct ContentView: View {
#Environment(\.editMode) var editMode
#State private var selections: [String] = []
#State private var colors: ["Red", "Yellow", "Blue"]
private var isEditing: Bool {
if editMode?.wrappedValue.isEditing == true {
return true
}
return false
}
var body: some View {
List(selection: $selections) {
ForEach(colors, id: \.self) { color in
Text("Color")
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
}
.onChange(of: isEditing) { value in
if value == false {
selection.removeAll()
}
}
}
}
In case someone want to use SwiftUI's EditButton() instead of custom a Button and still want to perform action when isEditing status changes
You can use View extension
extension View {
func onChangeEditMode(editMode: EditMode?, perform: #escaping (EditMode?)->()) -> some View {
ZStack {
Text(String(describing: editMode))
.opacity(0)
.onChange(of: editMode, perform: perform)
self
}
}
}
Then you can use it like this
struct TestEditModeView: View {
#Environment(\.editMode) var editMode
#State private var editModeDescription: String = "nil"
var body: some View {
VStack {
Text(editModeDescription)
EditButton()
}
.onChangeEditMode(editMode: editMode?.wrappedValue) {
editModeDescription = String(describing: $0)
}
}
}

Dismiss sheet SwiftUI

I'm trying to implement a dismiss button for my modal sheet as follows:
struct TestView: View {
#Environment(\.isPresented) var present
var body: some View {
Button("return") {
self.present?.value = false
}
}
}
struct DataTest : View {
#State var showModal: Bool = false
var modal: some View {
TestView()
}
var body: some View {
Button("Present") {
self.showModal = true
}.sheet(isPresented: $showModal) {
self.modal
}
}
}
But the return button when tapped does nothing. When the modal is displayed the following appears in the console:
[WindowServer] display_timer_callback: unexpected state (now:5fbd2efe5da4 < expected:5fbd2ff58e89)
If you force unwrap present you find that it is nil
How can I dismiss .sheet programmatically?
iOS 15+
Starting from iOS 15 we can use DismissAction that can be accessed as #Environment(\.dismiss).
There's no more need to use presentationMode.wrappedValue.dismiss().
struct SheetView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
NavigationView {
Text("Sheet")
.toolbar {
Button("Done") {
dismiss()
}
}
}
}
}
Use presentationMode from the #Environment.
Beta 6
struct SomeView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Text("Ohay!")
Button("Close") {
self.presentationMode.wrappedValue.dismiss()
}
}
}
}
For me, beta 4 broke this method - using the Environment variable isPresented - of using a dismiss button. Here's what I do nowadays:
struct ContentView: View {
#State var showingModal = false
var body: some View {
Button(action: {
self.showingModal.toggle()
}) {
Text("Show Modal")
}
.sheet(
isPresented: $showingModal,
content: { ModalPopup(showingModal: self.$showingModal) }
)
}
}
And in your modal view:
struct ModalPopup : View {
#Binding var showingModal:Bool
var body: some View {
Button(action: {
self.showingModal = false
}) {
Text("Dismiss").frame(height: 60)
}
}
}
Apple recommend (in WWDC 2020 Data Essentials in SwiftUI) using #State and #Binding for this. They also place the isEditorPresented boolean and the sheet's data in the same EditorConfig struct that is declared using #State so it can be mutated, as follows:
import SwiftUI
struct Item: Identifiable {
let id = UUID()
let title: String
}
struct EditorConfig {
var isEditorPresented = false
var title = ""
var needsSave = false
mutating func present() {
isEditorPresented = true
title = ""
needsSave = false
}
mutating func dismiss(save: Bool = false) {
isEditorPresented = false
needsSave = save
}
}
struct ContentView: View {
#State var items = [Item]()
#State private var editorConfig = EditorConfig()
var body: some View {
NavigationView {
Form {
ForEach(items) { item in
Text(item.title)
}
}
.navigationTitle("Items")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: presentEditor) {
Label("Add Item", systemImage: "plus")
}
}
}
.sheet(isPresented: $editorConfig.isEditorPresented, onDismiss: {
if(editorConfig.needsSave) {
items.append(Item(title: editorConfig.title))
}
}) {
EditorView(editorConfig: $editorConfig)
}
}
}
func presentEditor() {
editorConfig.present()
}
}
struct EditorView: View {
#Binding var editorConfig: EditorConfig
var body: some View {
NavigationView {
Form {
TextField("Title", text:$editorConfig.title)
}
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button(action: save) {
Text("Save")
}
.disabled(editorConfig.title.count == 0)
}
ToolbarItem(placement: .cancellationAction) {
Button(action: dismiss) {
Text("Dismiss")
}
}
}
}
}
func save() {
editorConfig.dismiss(save: true)
}
func dismiss() {
editorConfig.dismiss()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(items: [Item(title: "Banana"), Item(title: "Orange")])
}
}