SwiftUI Modal Badness
I’ve had a longstanding issue in Retrospective Timelines. I have a simple EditButton on the list of timelines in my app. This button toggles the list into edit mode where the user can perform several actions.
- Reorder timelines
- Delete a timeline
- Tap a button to open the timeline in a modal view so they can edit it.
The issue was that when I closed the modal view with a button, sometimes the EditButton on the list view would stop working. Looking at the view hierarchy in debug mode I could see that a containing object for the button was way out of position, and thus the button would stop working. I could sometimes get it snap back into position by swiping up and down on the list, but that is not really a viable solution. I needed a way to solve this.
This is a video I send to Apple as part of the feedback report (FB7265174). You can see the out-of-place container object.
I was once again banging my head on this issue today, trying to see if I could come up with a solution. While working through some options, I noticed that this only happed to SwiftUI Buttons, not to other types of objects in the navigation bar. I made a simple Text view and gave it an onTagGuesture modifier with a call to toggle the edit mode.
Text(self.listEditMode.isEditing ? "Done" : "Edit")
.onTapGesture {
self.listEditMode.isEditing ? .inactive : .active
}
This worked, but I lost the nice animations that SwiftUI was taking care of for me. I came across this post on StackOverflow and modified my view to use this bool version of Edit Mode instead of the the default one that SwiftUI provides. Here is a rough approximation
struct ContentView: View {
@State var isEditing = false
var names = ["Item 1", "Item 2", "Item 3"]
var body: some View {
NavigationView {
VStack {
List(names, id: \.self) { name in
Text(name)
}
.environment(\.editMode, .constant(self.isEditing ? EditMode.active : EditMode.inactive)).animation(Animation.spring())
}
.navigationBarItems(trailing: trailingButton)
}
}
private var trailingButton: some View {
Text(self.isEditing == true ? "Done" : "Edit")
.contentShape(Rectangle())
.frame(height: 44, alignment: .trailing)
.onTapGesture {
self.$isEditing.wrappedValue.toggle()
}
}
}
This sample code strips away all of the additional logic from my Timeline List view, but it shows how to implement the alternative version of the edit button as a Text view. Finally I have a reliable way to toggle edit mode in SwiftUI.