SwiftUI reuses List View when it should draw a completely different one (possible bug?)

I created a List for a settings menu with two sections where you can reorder/add/remove a set of pages. It should work similar to Apple's Today Widget editing menu where you can add and reorder items.


Connector class code snippet:


class SettingsConnector: ObservableObject {
     enum DetailsPage {
          case page1
          case page2
          case page3

          func printed() -> String {
               switch self {     
               case .page1:     
                    return "Page 1"          
               case .page2:
                    return "Page 2"     
               case .page3:     
                    return "Page 3"
               }
          }
     }

  @Published var detailsPages: [DetailsPage] = [.page1, .page2]
  @Published var hiddenDetailsPages: [DetailsPage] = [.page3]
}


SwiftUI code snippet:


List {
  Section {
       ForEach(self.connector.detailsPages, id: \.self) { page in
            HStack(spacing: 0) {
                 Text("\(page.printed())")
                 Spacer()
            }
       }
       .onMove { (source, destination) in
            self.connector.detailsPages.move(fromOffsets: source, toOffset: destination)
       }
       .onDelete { (rows) in
            rows.forEach { (i) in
                 let page: SettingsConnector.DetailsPage = self.connector.detailsPages[i]
                 self.connector.detailsPages.remove(at: i)
                 self.connector.hiddenDetailsPages.insert(page, at: 0
            }
        }
  }


  Section {
       ForEach(self.connector.hiddenDetailsPages, id: \.self) { page in
            HStack(spacing: 0) {
                 Image(systemName: "plus.circle.fill")
                      .foregroundColor(.green)
                      .padding(.horizontal, 5)

                 Text("Add \(page.printed())")
                      .padding(.horizontal, 5)
                 Spacer()
                
            }

       }
  }
}


When I delete a page on the first section, it does appear in the hiddenDetailsPages sections.


However, even though there is no direct relation between the sections, the cell view is just moved to the second section but using the drawing code from the first section. The drawing instructions from the second section are simply not used even though the cell appears in that section.


Am I getting something completely wrong or is this a bug in SwiftUI?

Answered by Claude31 in 417543022

There still misses definition of connector in ContentView.

Is it

@State private var connector = SettingsConnector()


If that's what you did, replace with ObservedObject

@ObservedObject private var connector = SettingsConnector()


Please, give the complete and exact code.


Here is what I tested (delete works better)


import SwiftUI

class SettingsConnector: ObservableObject {
     enum DetailsPage {
          case page1
          case page2
          case page3
 
          func printed() -> String {
               switch self {
               case .page1:
                    return "Page 1"
               case .page2:
                    return "Page 2"
               case .page3:
                    return "Page 3"
               }
          }
     }
 
  @Published var detailsPages: [DetailsPage] = [.page1, .page2]
  @Published var hiddenDetailsPages: [DetailsPage] = [.page3]
}

struct ContentView: View {
    @ObservedObject private var connector = SettingsConnector()

    var body: some View {
        List {
            Section {
                ForEach(self.connector.detailsPages, id: \.self) { page in
                    HStack(spacing: 0) {
                        Text("\(page.printed())")
                        Spacer()
                    }
                }
                .onMove { (source, destination) in
                    self.connector.detailsPages.move(fromOffsets: source, toOffset: destination)
                }
                .onDelete { rows in
                    rows.forEach { i in
                        let page: SettingsConnector.DetailsPage = self.connector.detailsPages[i]
                        self.connector.detailsPages.remove(at: i)
                        self.connector.hiddenDetailsPages.insert(page, at: 0)
                    }
                }
            }
           
            Section {
                VStack{
                    Text("Hidden \(self.connector.hiddenDetailsPages.count)")
                    ForEach(self.connector.hiddenDetailsPages, id: \.self) { page in
                        HStack(spacing: 0) {
                            Image(systemName: "plus.circle.fill")
                                .foregroundColor(.green)
                                .padding(.horizontal, 5)
                           
                            Text("Add \(page.printed())")
                                .padding(.horizontal, 5)
                            Spacer()
                        }
                    }
                }
               
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


Notes:

- Added print line 51 for check

- VStack line 50 is needed, otherwise, section is not redrawn !

Fear that's a problem in List.

I will file a bug report, unless someone has an explantion for this.


To be able to move items, I had to add a NavigationView

(see this: h ttps://medium.com/programming-with-swift/move-list-item-with-swiftui-b21fdc3cba3d):


struct ContentView: View {
    @ObservedObject private var connector = SettingsConnector()
   
    var body: some View {
        NavigationView {          // NEEDED TO MOVE ITEMS
            List {
                Section {
                    ForEach(self.connector.detailsPages, id: \.self) { page in
                        HStack(spacing: 0) {
                            Text("\(page.printed())")
                            Spacer()
                        }
                    }
                    .onMove { (source, destination) in
                        self.connector.detailsPages.move(fromOffsets: source, toOffset: destination)
                    }
                    .onDelete { rows in
                        rows.forEach { i in
                            let page: SettingsConnector.DetailsPage = self.connector.detailsPages[i]
                            self.connector.detailsPages.remove(at: i)
                            self.connector.hiddenDetailsPages.insert(page, at: 0)
                        }
                    }
                }
               
                //            Spacer()
                //
                Section {
                    VStack{
                        Text("Hidden \(self.connector.hiddenDetailsPages.count)")
                        ForEach(self.connector.hiddenDetailsPages, id: \.self) { page in
                            HStack(spacing: 0) {
                                Image(systemName: "plus.circle.fill")
                                    .foregroundColor(.green)
                                    .padding(.horizontal, 5)
                               
                                Text("Add \(page.printed())")
                                    .padding(.horizontal, 5)
                                Spacer()
                            }
                        }
                    }
                   
                }
            }
            .navigationBarTitle(Text("Nav Title"))
            .navigationBarItems(trailing: EditButton())
        }
    }
}



With the addition of Navigation (line 05), if we suppress VSTack lines 29-31, then onDelete, rows moves to second section, but the + is not drawn ?!

definetely, there is some mysterious interaction between List and VStack…

Did you copy exactly the code ?

I notice a missing closing parenthesis on line 16.


How is connector defined ? Impossible to test without it.

I have added the relevant part of the surrounding class for completeness, thank you.

Accepted Answer

There still misses definition of connector in ContentView.

Is it

@State private var connector = SettingsConnector()


If that's what you did, replace with ObservedObject

@ObservedObject private var connector = SettingsConnector()


Please, give the complete and exact code.


Here is what I tested (delete works better)


import SwiftUI

class SettingsConnector: ObservableObject {
     enum DetailsPage {
          case page1
          case page2
          case page3
 
          func printed() -> String {
               switch self {
               case .page1:
                    return "Page 1"
               case .page2:
                    return "Page 2"
               case .page3:
                    return "Page 3"
               }
          }
     }
 
  @Published var detailsPages: [DetailsPage] = [.page1, .page2]
  @Published var hiddenDetailsPages: [DetailsPage] = [.page3]
}

struct ContentView: View {
    @ObservedObject private var connector = SettingsConnector()

    var body: some View {
        List {
            Section {
                ForEach(self.connector.detailsPages, id: \.self) { page in
                    HStack(spacing: 0) {
                        Text("\(page.printed())")
                        Spacer()
                    }
                }
                .onMove { (source, destination) in
                    self.connector.detailsPages.move(fromOffsets: source, toOffset: destination)
                }
                .onDelete { rows in
                    rows.forEach { i in
                        let page: SettingsConnector.DetailsPage = self.connector.detailsPages[i]
                        self.connector.detailsPages.remove(at: i)
                        self.connector.hiddenDetailsPages.insert(page, at: 0)
                    }
                }
            }
           
            Section {
                VStack{
                    Text("Hidden \(self.connector.hiddenDetailsPages.count)")
                    ForEach(self.connector.hiddenDetailsPages, id: \.self) { page in
                        HStack(spacing: 0) {
                            Image(systemName: "plus.circle.fill")
                                .foregroundColor(.green)
                                .padding(.horizontal, 5)
                           
                            Text("Add \(page.printed())")
                                .padding(.horizontal, 5)
                            Spacer()
                        }
                    }
                }
               
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


Notes:

- Added print line 51 for check

- VStack line 50 is needed, otherwise, section is not redrawn !

Fear that's a problem in List.

I will file a bug report, unless someone has an explantion for this.


To be able to move items, I had to add a NavigationView

(see this: h ttps://medium.com/programming-with-swift/move-list-item-with-swiftui-b21fdc3cba3d):


struct ContentView: View {
    @ObservedObject private var connector = SettingsConnector()
   
    var body: some View {
        NavigationView {          // NEEDED TO MOVE ITEMS
            List {
                Section {
                    ForEach(self.connector.detailsPages, id: \.self) { page in
                        HStack(spacing: 0) {
                            Text("\(page.printed())")
                            Spacer()
                        }
                    }
                    .onMove { (source, destination) in
                        self.connector.detailsPages.move(fromOffsets: source, toOffset: destination)
                    }
                    .onDelete { rows in
                        rows.forEach { i in
                            let page: SettingsConnector.DetailsPage = self.connector.detailsPages[i]
                            self.connector.detailsPages.remove(at: i)
                            self.connector.hiddenDetailsPages.insert(page, at: 0)
                        }
                    }
                }
               
                //            Spacer()
                //
                Section {
                    VStack{
                        Text("Hidden \(self.connector.hiddenDetailsPages.count)")
                        ForEach(self.connector.hiddenDetailsPages, id: \.self) { page in
                            HStack(spacing: 0) {
                                Image(systemName: "plus.circle.fill")
                                    .foregroundColor(.green)
                                    .padding(.horizontal, 5)
                               
                                Text("Add \(page.printed())")
                                    .padding(.horizontal, 5)
                                Spacer()
                            }
                        }
                    }
                   
                }
            }
            .navigationBarTitle(Text("Nav Title"))
            .navigationBarItems(trailing: EditButton())
        }
    }
}



With the addition of Navigation (line 05), if we suppress VSTack lines 29-31, then onDelete, rows moves to second section, but the + is not drawn ?!

definetely, there is some mysterious interaction between List and VStack…

Encapsulatin in a VStack did the trick. Thank you!

Looks like a bug to me, since the VStack does not make any sense within the structure of the UI here.

Thanks for filing a bug report!

Filed a bug report : Apr 30, 2020 at 9:09 PM – FB7682787

SwiftUI reuses List View when it should draw a completely different one (possible bug?)
 
 
Q