2

I am trying to reload tableView after I have a response from API. TableView reload does not call cellForRowAt as initially it was initialized with empty items. I have added the minimum required code to replicate the issue.

Code for UIViewRepresentable

import SwiftUI
import UIKit

struct UIListView: UIViewRepresentable {
    
    @Binding var reload: Bool
    
    var items: [String]
    private let tableView =  UITableView()
    
    func makeUIView(context: Context) -> UITableView {
        print("*************** make")
        tableView.dataSource = context.coordinator
        return tableView
    }
    
    func updateUIView(_ uiView: UITableView, context: Context) {
        print("*************** update", items.count)
        print("********* RELOAD", reload)
        if reload {
            DispatchQueue.main.async {
                //                uiView.reloadData()
                //                tableView.reloadData()
                context.coordinator.reload()
            }
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
}

extension UIListView {
    
    final class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {
        
        private var parent: UIListView
        
        init(_ parent: UIListView) {
            self.parent = parent
        }
        
        func reload() {
            parent.tableView.reloadData()
        }
        
        //MARK: UITableViewDataSource Methods
        
        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return parent.items.count
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            print("*********Cell for row at")
            let cell = UITableViewCell()
            cell.textLabel?.text = parent.items[indexPath.row]
            return cell
        }
    }
}

Code for CustomListView and ViewModel

    struct CustomListView: View {
    @ObservedObject var model = VM()
    
    var body: some View {
        print("******* BODY")
        return UIListView(reload: $model.reload, items: model.items)
            .onAppear {
                model.fetchData()
            }
    }
}

class VM: ObservableObject {
    
    @Published var reload = false
    var items = [String]()
    
    func fetchData() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            self.items = ["asdas", "asdasdas"]
            self.reload.toggle()
        }
    }
}
user832
  • 796
  • 5
  • 18

1 Answers1

5

I assume the UITableView member just recreated, because it is member, instead use as below

Tested with Xcode 12 / iOS 14

struct CustomListView: View {
    @StateObject var model = VM()

    var body: some View {
        print("******* BODY")
        return UIListView(reload: $model.reload, items: $model.items)
            .onAppear {
                model.fetchData()
            }
    }
}

class VM: ObservableObject {

    @Published var reload = false
    var items = [String]()

    func fetchData() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
            self.items = ["asdas", "asdasdas"]
            self.reload.toggle()
        }
    }
}

struct UIListView: UIViewRepresentable {

    @Binding var reload: Bool
    @Binding var items: [String]

    func makeUIView(context: Context) -> UITableView {
        print("*************** make")
        let tableView =  UITableView()
        tableView.dataSource = context.coordinator
        return tableView
    }

    func updateUIView(_ tableView: UITableView, context: Context) {
        print("*************** update", items.count)
        print("********* RELOAD", reload)
        if reload {
            DispatchQueue.main.async {
                tableView.reloadData()
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
}

extension UIListView {

    final class Coordinator: NSObject, UITableViewDataSource, UITableViewDelegate {

        private var parent: UIListView

        init(_ parent: UIListView) {
            self.parent = parent
        }

        //MARK: UITableViewDataSource Methods

        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }

        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return parent.items.count
        }

        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            print("*********Cell for row at")
            let cell = UITableViewCell()
            cell.textLabel?.text = parent.items[indexPath.row]
            return cell
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Does it work for you? It doesn't work for me on Xcode beta 3 already tried the way you mentioned. – user832 Aug 06 '20 at 15:43
  • Great that workes. Do you have any explanation as to why just using reload as Binding not working? – user832 Aug 06 '20 at 16:13
  • `items` were value and part of struct, so on `Coordinator` init just copied empty and stored it. Binding gives access to updatable storage of data. – Asperi Aug 06 '20 at 16:16
  • Now it made sense to me, so that means if I remove reload Binding, it should still work. – user832 Aug 06 '20 at 17:50