I am building an app for iOS and Mac Catalyst and have been able to code most of the experience that I want except for functions that use swipe to delete in iOS.
The view includes multiple sections, each with a List and ForEach closure. I want to be able to add the EditButton() function to the header of each section and have it apply only to that section's List.
I can add an EditButton() function to gain this functionality, however, so far I have only been able to make that work for the entire screen, not for the individual sections.
I have tried refactoring the code for each section into functions and into structs (as shown below). In all cases the EditButton() activates the delete icons for ALL list rows, not just the section with the button.
I have also tried placing the EditButton() inside the section in the VStack. No difference.
Here's a simple example with the latest code attempt:
struct ContentView: View {
@State private var selectedItem: String?
@State private var items = ["One", "Two", "Three", "Four", "Five"]
@State private var fruits = ["Apple", "Orange", "Pear", "Lemon", "Grape"]
var body: some View {
NavigationSplitView {
ItemSection(selectedItem: $selectedItem, items: $items)
FruitSection(selectedItem: $selectedItem, fruits: $fruits)
} detail: {
if let selectedItem {
ItemDetailView(selectedItem: selectedItem)
} else {
EmptyView()
}
}//nav
}//body
}//struct
struct ItemSection: View {
@Binding var selectedItem: String?
@Binding var items: [String]
var body: some View {
Section {
VStack {
List(selection: $selectedItem) {
ForEach(items, id: \.self) { item in
NavigationLink(value: item) {
Text(item)
}
}
.onDelete { items.remove(atOffsets: $0) }
}
.listStyle(PlainListStyle())
}//v
.padding()
} header: {
HStack {
Text("Section for Items")
Spacer()
//uncomment when you have it working
//#if targetEnvironment(macCatalyst)
EditButton()
//#endif
}//h
.padding(.horizontal, 10)
}//section and header
}//body
}//item section
struct FruitSection: View {
@Binding var selectedItem: String?
@Binding var fruits: [String]
var body: some View {
Section {
VStack {
List(selection: $selectedItem) {
ForEach(fruits, id: \.self) { fruit in
NavigationLink(value: fruit) {
Text(fruit)
}
}
.onDelete { fruits.remove(atOffsets: $0) }
}
.listStyle(PlainListStyle())
}//v
.padding()
} header: {
HStack {
Text("Section for Fruits")
Spacer()
}//h
.padding(.horizontal, 10)
}//section fruit
}//body
}//fruit section
struct ItemDetailView: View {
var selectedItem: String
var body: some View {
VStack {
Text(selectedItem)
Text("This is the DetailView")
}
}
}
Any guidance would be appreciated. Xcode 14.0.1 iOS 16
CodePudding user response:
import SwiftUI
struct ContentView: View {
@State private var selectedItem: String?
@State private var items = ["One", "Two", "Three", "Four", "Five"]
@State private var fruits = ["Apple", "Orange", "Pear", "Lemon", "Grape"]
var body: some View {
NavigationSplitView {
ItemSection(selectedItem: $selectedItem, items: $items)
FruitSection(selectedItem: $selectedItem, fruits: $fruits)
} detail: {
if let selectedItem {
ItemDetailView(selectedItem: selectedItem)
} else {
EmptyView()
}
}//nav
}//body
}//struct
struct ItemSection: View {
@Binding var selectedItem: String?
@Binding var items: [String]
@State var isEditMode = false // this is what you need
var body: some View {
Section {
VStack {
List(selection: $selectedItem) {
ForEach(items, id: \.self) { item in
NavigationLink(value: item) {
Text(item)
}
}
.onDelete { items.remove(atOffsets: $0) }
}
.environment(\.editMode, isEditMode ? .constant(.active) : .constant(.inactive)) // and set this
.listStyle(PlainListStyle())
}//v
.padding()
} header: {
HStack {
Text("Section for Items")
Spacer()
//uncomment when you have it working
//#if targetEnvironment(macCatalyst)
Button { // you also need to set EditButton() -> Button()
withAnimation {
isEditMode.toggle()
}
} label: {
Text(isEditMode ? "Done" : "Edit")
}
//#endif
}//h
.padding(.horizontal, 10)
}//section and header
}//body
}//item section
struct FruitSection: View {
@Binding var selectedItem: String?
@Binding var fruits: [String]
@State var isEditMode = false // same as this section
var body: some View {
Section {
VStack {
List(selection: $selectedItem) {
ForEach(fruits, id: \.self) { fruit in
NavigationLink(value: fruit) {
Text(fruit)
}
}
.onDelete { fruits.remove(atOffsets: $0) }
}
.environment(\.editMode, isEditMode ? .constant(.active) : .constant(.inactive))
.listStyle(PlainListStyle())
}//v
.padding()
} header: {
HStack {
Text("Section for Fruits")
Spacer()
Button {
withAnimation {
isEditMode.toggle()
}
} label: {
Text(isEditMode ? "Done" : "Edit")
}
}//h
.padding(.horizontal, 10)
}//section fruit
}//body
}//fruit section
struct ItemDetailView: View {
var selectedItem: String
var body: some View {
VStack {
Text(selectedItem)
Text("This is the DetailView")
}
}
}
CodePudding user response:
Here's a more general & simplified approach using PreferenceKey
:
struct EditModeViewModifier: ViewModifier {
var forceEditing: Bool?
@State var isEditing = false
func body(content: Content) -> some View {
content
.onPreferenceChange(IsEditingPrefrenceKey.self) { newValue in
withAnimation {
isEditing = newValue
}
}.environment(\.editMode, .constant((forceEditing ?? isEditing) ? .active: .inactive))
}
}
extension View {
func editMode(_ editing: Bool? = nil) -> some View {
modifier(EditModeViewModifier(forceEditing: editing))
}
}
struct EditingButton: View {
@State var isEditing = false
var body: some View {
Button(action: {
isEditing.toggle()
}) {
Text(isEditing ? "Done" : "Edit")
}.preference(key: IsEditingPrefrenceKey.self, value: isEditing)
}
}
struct IsEditingPrefrenceKey: PreferenceKey {
static var defaultValue = false
static func reduce(value: inout Bool, nextValue: () -> Bool) {
value = nextValue()
}
}
You use EditingButton() instead of EditButton()
, & use .editMode()
at then end of your View
. Then your sections become something like this:
struct ItemSection: View {
@Binding var selectedItem: String?
@Binding var items: [String]
var body: some View {
Section {
VStack {
List(selection: $selectedItem) {
ForEach(items, id: \.self) { item in
NavigationLink(value: item) {
Text(item)
}
}.onDelete { items.remove(atOffsets: $0) }
}.listStyle(PlainListStyle())
}.padding()
} header: {
HStack {
Text("Section for Items")
Spacer()
//uncomment when you have it working
//#if targetEnvironment(macCatalyst)
EditingButton()
//#endif
}.padding(.horizontal, 10)
}.editMode()
}
}
struct FruitSection: View {
@Binding var selectedItem: String?
@Binding var fruits: [String]
var body: some View {
Section {
VStack {
List(selection: $selectedItem) {
ForEach(fruits, id: \.self) { fruit in
NavigationLink(value: fruit) {
Text(fruit)
}
}.onDelete { fruits.remove(atOffsets: $0) }
}.listStyle(PlainListStyle())
}.padding()
} header: {
HStack {
Text("Section for Fruits")
Spacer()
EditingButton()
}.padding(.horizontal, 10)
}.editMode()
}
}