0

I'm stuck on this very basic task where I have a tableView and I want whenever the user clicks on the button it will remove the last element and update it on the tableview. I've tried the following but for some reason, this doesn't work. I assume it's something to do with the @state? it works when I select an element but whenever I click the button it ignores it

struct ContentView: View {
    
    var tableView = CustomTableView()
    
    var body: some View {
        VStack{
            tableView
            Button("hello", action: tableView.removeElement)
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    struct CustomTableView : UIViewRepresentable {
        
        @State var items = ["hello", "world", "great", "day"]
        let table = UITableView()

        
        func removeElement() {
            items.removeLast()
            print(items.count)
            table.reloadData()
        }
        func makeUIView(context: Context) -> UITableView {
            table.delegate = context.coordinator
            table.dataSource = context.coordinator
            return table
        }
        
        func updateUIView(_ table: UITableView, context: Context) {
            table.separatorStyle = .none
        }
        
        func makeCoordinator() -> Coordinator {
            return Coordinator(self)
        }
    }
    
    class Coordinator : NSObject, UITableViewDataSource, UITableViewDelegate {
        
        var parent: CustomTableView
        
        init(_ parent: CustomTableView) {
            self.parent = parent
        }
        
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            let selectedItem = parent.items[indexPath.row]
            parent.items.removeAll { (item) -> Bool in
                return item == selectedItem
            }
            tableView.reloadData()
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return parent.items.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
            cell.textLabel?.text = parent.items[indexPath.row]
            return cell
        }
        
    }
}
Neta Yamin
  • 33
  • 2
  • 6

1 Answers1

0

There were a few problems going on.

  1. Holding a reference to parent wasn't going to work to get items because the parent was a struct and thus passed value and not reference. So, the Coordinator was always going to get the original version of items and not be updated

  2. Creating one reference to your CustomTableView and then reusing it works against the SwiftUI principals of how the view hierarchy is created and used. Better to define it each time and pass it new props. So, I've moved @State items to the top view and pass it down through the hierarchy. It gets passed from ContentView to CustomTableView to the Coordinator

struct ContentView: View {
    
    @State var items = ["hello", "world", "great", "day"] //<-- Here
    
    var body: some View {
        VStack{
            CustomTableView(items: items)
            Button("hello", action: { items.removeLast() })  //<-- Here
        }
    }
}

struct CustomTableView : UIViewRepresentable {
    var items : [String]
    
    func makeUIView(context: Context) -> UITableView {
        let table = UITableView()  //<-- Here
        table.delegate = context.coordinator
        table.dataSource = context.coordinator
        return table
    }
    
    func updateUIView(_ table: UITableView, context: Context) {
        table.separatorStyle = .none
        context.coordinator.items = items  //<-- Here
        table.reloadData()
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
    
    class Coordinator : NSObject, UITableViewDataSource, UITableViewDelegate {
        var items : [String] = []  //<-- Here
        
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            let selectedItem = items[indexPath.row]
            items.removeAll { (item) -> Bool in
                return item == selectedItem
            }
            tableView.reloadData()
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return items.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
            cell.textLabel?.text = items[indexPath.row]
            return cell
        }
        
    }
}

What this solution doesn't currently get you is the fancy table view animations would something is removed. You have a couple of options for this:

  1. Use SwiftUI's List instead of wrapping UITableView
  2. Instead of @State, use an ObservableObject that is passed by reference instead of value and treat that as the source of your data. Then, you could have your Coordinator refresh the table view without actually recreating it when the data source changes. I'm sure it could be achieved with @State, too, but at the least I feel like it's going against the grain of how SwiftUI generally works and structures data.
jnpdx
  • 45,847
  • 6
  • 64
  • 94
  • yeah, but if I use List I'm limited to the styling options.. I want to remove the separator for example – Neta Yamin Feb 25 '21 at 17:25
  • https://stackoverflow.com/questions/56553672/how-to-remove-the-line-separators-from-a-list-in-swiftui-without-using-foreach – jnpdx Feb 25 '21 at 17:28
  • yeah i saw this when i first built It in List view but this solution didn't work... I prefer using UIKit for this as it's pretty simple and i get full control btw I found a way to solve this and achieve this with animation simply delete rows like this and you will get animation table.deleteRows(at: [index], with: .fade) – Neta Yamin Feb 25 '21 at 19:15
  • Great. If this answer helped you on your way, feel free to upvote or accept – jnpdx Feb 25 '21 at 19:21