|
I'm working on an app which uses NavigationView and NavigationLinks to drill down into data. Below is an extremely simple app to demonstrate the problem(s).
The real intent is to display a list of names. Clicking on a category would navigate to a detailed view of the data for that name. This works fine. I also want to be able to edit the name, but want to do so from the list view, not the detail view. Using the example below, I almost have it working except for some issues.
- The first time I tap on the edit symbol, the first name always appears on the sheet view. After that, it works as intended.
- If I bring up the sheet view and then dismiss it, the EditButton doesn't respond to taps (and continues to display "Done"). Tapping on the button a few times finally works.
I guess I'm looking for help on 2 counts.
- Any help in solving the issues I'm seeing is appreciated, including alternative ways to accomplish the goal.
- Any recommendation on UI design for this would also be appreciated. (One thought I've had is to make the names list A TextField (initially disabled) instead of a Text view.)
//
// ContentView.swift
// testNav
//
// Created by Arnold Weekley on 9/11/21.
//
import SwiftUI
struct ContentView: View {
@State var names = ["Eric", "Elton", "John", "Paul", "George", "Ringo"]
@State private var editMode: EditMode = EditMode.inactive
@State var showingEditSheet = false
@State var selected = 0
var body: some View {
NavigationView {
List {
ForEach(names.indices, id:\.self) { index in
HStack {
if editMode.isEditing {
Image(systemName: "square.and.pencil")
.padding(5)
.onTapGesture {
selected = index
showingEditSheet = true
}
}
NavigationLink(destination: DetailView(names: names, index: index)) {
Text("\(names[index])")
.padding(.leading)
}
}
}
.onDelete(perform: deleteName)
}
.navigationTitle("Categories")
.navigationBarItems(trailing: EditButton())
.environment(\.editMode, $editMode)
.animation(.default)
.sheet(isPresented: $showingEditSheet) {
EditView(names: $names, index: selected)
}
}
}
private func deleteName(offsets: IndexSet) {
withAnimation {
offsets.sorted(by: > ).forEach { (idx) in
names.remove(at: idx)
}
}
}
}
struct DetailView: View {
var names: [String]
var index: Int
var body: some View {
Text("Name is: \(names[index])")
.navigationTitle("Detail")
.navigationBarTitleDisplayMode(.inline)
}
}
struct EditView: View {
@Environment (\.presentationMode) var presentationMode
@Binding var names: [String]
var index: Int
var body: some View {
VStack {
Text("Edit name...")
TextField("name...", text: $names[index])
.textFieldStyle(RoundedBorderTextFieldStyle())
.navigationTitle("Editor")
.navigationBarTitleDisplayMode(.inline)
.padding(.bottom)
Button("Cancel", action: {
self.presentationMode.wrappedValue.dismiss()
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
|
|
First I assume you are want this in iOS 14 as with the new swipe button in iOS 15 would be a better way to go.
How to add custom swipe action buttons to a List row
If you still want to support lower then iOS 15 then I would a "EDIT" button (not the editMode) in the link to able user to edit the text
EG
struct DetailView: View {
@Binding var names: [String]
var index: Int
@State private var isEditing = false
var body: some View {
VStack {
if isEditing {
TextField(names[index], text: $names[index])
.textFieldStyle(RoundedBorderTextFieldStyle())
} else {
HStack {
Text(names[index])
Spacer()
}
}
}
.padding()
.navigationTitle(isEditing ? "Editor" : "Detail")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Button(isEditing ? "Done" : "Edit") {
isEditing.toggle()
}
}
}
}
Leaving the EditMode to do delete and move in List as it was design to do
|
|
@NigelGee, thanks for the reply.
I had forgot to say that I was still on IOS 14. What you recommended is correct, but I realize I wasn't clear on what I'm attempting to do. I had worked out code that shows the full problem scope but attempted to dumb it down for submission.
What I want to do is be able to drill down to a detail screen. Handling the drill down is easy enough. See attached images of what I have so far in the app.
- My initial list shows various categories (i.e. "Golf Courses", "Parks", "Restaurants", etc...).
- When I click on a category to show detail, the next view contains a list of items within the selected category.
- Finally, if I click on one of the items in that list, the detail is shown and the "Edit" button you suggest would be the correct solution.
My problem is that I want to be able to edit the category names (i.e. to correct spelling) and it didn't make sense to do that from the next screen. Hence the edit image by the category names. Thinking about it, perhaps the solution may be just to leave the edit button showing
Top Level Categories - Want to drill down or edit category name
Second level from selection above - Again, want to drill down or edit name
Bottom level list - From here, I can just drill down to item detail
Item detail - Can add edit button to switch to edit mode on this screen
|
|
I've updated the code to get rid of the editMode. It mostly works now, except for...
- Clicking on the edit pencil takes me to edit Eric regardless which name I click on.
- Canceling and then clicking on the same name still shows Eric.
- Canceling and then clicking on a different name shows the correct name.
- I discovered that if I attempt to edit Eric when it shows up incorrectly causes the TextField to update to the correct name as soon as any change is made.
Below is the updated code.
//
// ContentView.swift
// testNav
//
// Created by Arnold Weekley on 9/11/21.
//
import SwiftUI
struct ContentView: View {
@State var names = ["Eric", "Elton", "John", "Paul", "George", "Ringo"]
@State var showingEditSheet = false
@State var selected = 0
var body: some View {
NavigationView {
List {
ForEach(names.indices, id:\.self) { index in
HStack {
Button(action: {
print("before: selected = \(selected), index = \(index)")
selected = index
print("after: selected = \(selected), index = \(index)")
showingEditSheet = true
}) {
Label("", systemImage: "square.and.pencil")
}
.onTapGesture {
print("before selected: \(selected), index: \(index)")
selected = index
print("after selected: \(selected), index: \(index)")
showingEditSheet = true
}
NavigationLink(destination: DetailView(names: names, index: index)) {
Text("\(names[index])")
.padding(.leading)
}
}
}
.onDelete(perform: deleteName)
}
.navigationTitle("Categories")
.navigationBarItems(trailing: EditButton())
.animation(.default)
.sheet(isPresented: $showingEditSheet) {
EditView(names: $names, index: selected)
}
}
}
private func deleteName(offsets: IndexSet) {
withAnimation {
offsets.sorted(by: > ).forEach { (idx) in
names.remove(at: idx)
}
}
}
}
struct DetailView: View {
var names: [String]
var index: Int
var body: some View {
VStack {
Text("Name is: \(names[index])")
.padding(.bottom)
Text("Detail for editing here...")
}
.padding()
.navigationTitle("Detail")
.navigationBarTitleDisplayMode(.inline)
}
}
struct EditView: View {
@Environment (\.presentationMode) var presentationMode
@Binding var names: [String]
var index: Int
var body: some View {
VStack {
Text("Edit name...")
TextField("name...", text: $names[index])
.textFieldStyle(RoundedBorderTextFieldStyle())
.navigationTitle("Editor")
.navigationBarTitleDisplayMode(.inline)
.padding(.bottom)
Button("Cancel", action: {
self.presentationMode.wrappedValue.dismiss()
})
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
|
|
Update:
I discovered that if I replace
var index: Int
in the EditView struct with
@Binding var index: Int
The edit screen works as expected. However now, I see that clicking on a name in the list now brings up the edit view and then the detail view.
|
|
Noticed that I had both Button(action=... and onTapGesture for the button next to each name in the list. Anyway, replaced the Button with an Image with onTapGesture and everything now works as expected.
|