1
votes

I am working on an app and encountered a strange behavior of List and NavigationLink when removing the last item in the list from the detail view. I am using iOS 13 and Xcode 11 and I made a simplified version that reproduces the behavior:

import SwiftUI

struct ListView: View {
    @State private var content = [Int](0..<10) {
        didSet {
            print(content)
        }
    }

    var body: some View {
        NavigationView {
            List(content, id: \.self) { element in
                NavigationLink(
                    destination: DetailView(
                        remove: {
                            DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) { // Asynchronous network request
                                DispatchQueue.main.async {
                                    self.content.removeAll { $0 == element } // When request was successfull remove element from list
                                }
                            }
                        }
                    )
                ) {
                    Text("Element #\(element)")
                }
            }
        }
    }
}

struct DetailView: View {

    let remove: () -> Void

    init(remove: @escaping () -> Void) {
        self.remove = remove
    }

    var body: some View {
        VStack {
            Text("Hello world!")

            Button(
                action: {
                    self.remove()
                }
            ) {
                Image(systemName: "trash.circle")
                    .imageScale(.large)
            }
        }
    }
}

To reconstruct the error select the last item in the list and press the trashcan to delete. As you may notice the view does not disappear as with the other items from the list. But if you press back the list will have correctly removed the last item. This is also shown in this gif. The state change of the list is printed to the console when pressing the trashcan.

I have noticed that the removing of the specific item does not seem to be the problem as it also happens when removing a random item. It does work correctly if I remove the selected item and add new items at the end. So it may be caused by shrinking the size of the array.

I have also found several workarounds. Like modifying the NavigationView with .id(UUID()) but this removes the animations. An other solution is to dismiss the view with PresentationMode and on disappear remove the item from the list but I rather would use a different solution.

To see if it is related to iOS 13 or Xcode 11 I tested it on the newest beta version of iOS 14 and Xcode 12 (currently beta 5). Here the detail view does not get dismissed with any of the selected items.

Has anybody encountered this problem before or at least can explain why this behaves this way?

EDIT: Added mock network request to better illustrate the specific issue.

1

1 Answers

0
votes

SwiftUI behaves a bit strange there, I think...

You could tell SwiftUI explicitly to dismiss the DetailView. To do so, you need to modify DetailView a little:

struct DetailView: View {
    @Environment(\.presentationMode) var presentationMode
    
    let remove: () -> Void
    
    init(remove: @escaping () -> Void) {
        self.remove = remove
    }
    
    var body: some View {
        VStack {
            Text("Hello world!")
            
            Button(
                action: {
                    self.remove()
                    self.presentationMode.wrappedValue.dismiss()
                }
            ) {
                Image(systemName: "trash.circle")
                    .imageScale(.large)
            }
        }
    }
}

(See also: iOS SwiftUI: pop or dismiss view programmatically)

For me, this seems to work, even with the last row.