2
votes

I'm trying to implement table view with new DiffableDataSource api, but the cells simply do not load:

var tableView = UITableView()
    var currencyPairsArray = [String]()
    lazy var fetcher = NetworkDataFetcher()

    lazy var searchText = String()
    lazy var searchArray = [String]()
    lazy var searchController: UISearchController = {
        let controller = UISearchController(searchResultsController: nil)
        controller.hidesNavigationBarDuringPresentation = false
        controller.obscuresBackgroundDuringPresentation = false
        controller.searchBar.delegate = self
        return controller
    }()

    fileprivate var dataSource : UITableViewDiffableDataSource<Section, String>!

    var searchBarIsEmpty: Bool {
        return searchController.searchBar.text?.isEmpty ?? true
    }

    override func viewDidLoad() {
        super.viewDidLoad()


        setupVC()
        setupTableView()
        setupDataSource()
        performSearch(with: nil)


        fetcher.fetchCurrencyPairs { [weak self] pairsArray in
            self?.currencyPairsArray.append(contentsOf: pairsArray)
            DispatchQueue.main.async {
                self?.tableView.reloadData()
            }
        }
        //tableView.reloadData()

        // Do any additional setup after loading the view.
    }

    func setupVC() {
        view.backgroundColor = .white
        view.addSubview(tableView)
        navigationItem.title = "Currency pairs"
        navigationItem.searchController = searchController
        navigationItem.hidesSearchBarWhenScrolling = false

    }

    func setupTableView() {
        tableView.delegate = self
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.pinToSuperView()
        tableView.register(UINib(nibName: "CurrencyPairCell", bundle: nil), forCellReuseIdentifier: CurrencyPairCell.reuseIdentifier)
    }


    func setupDataSource() {
        dataSource = UITableViewDiffableDataSource<Section, String>(tableView: tableView, cellProvider: { [weak self] (tableView, indexPath, _) ->  UITableViewCell?  in
            let cell = tableView.dequeueReusableCell(withIdentifier: CurrencyPairCell.reuseIdentifier, for: indexPath) as! CurrencyPairCell
            cell.delegate = self
            let pair = self?.currencyPairsArray[indexPath.row].formattedPair()
            cell.currencyPairLabel.text = pair
            cell.currencyPair = self?.currencyPairsArray[indexPath.row] ?? ""
            return cell
        })
    }

    func performSearch(with filter: String?) {

        var snapshot = NSDiffableDataSourceSnapshot<Section, String>()

        if let filter = filter {
            let filteredPairs = currencyPairsArray.filter {$0.contains(filter)}

            snapshot.appendSections([.main])
            snapshot.appendItems(filteredPairs, toSection: .main)
            dataSource.apply(snapshot, animatingDifferences: true)
        } else {
            let pairs = currencyPairsArray.sorted()
            snapshot.appendSections([.main])
            snapshot.appendItems(pairs, toSection: .main)
            dataSource.apply(snapshot)
        }

    }

}

extension CurrencyListViewController: UISearchBarDelegate {

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

        performSearch(with: searchText)
    }

}

extension CurrencyListViewController {
    fileprivate enum Section: Hashable {
        case main
    }
}

Also, i am getting a warning from the xcode:

[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window. Table view: ; layer = ; contentOffset: {0, 0}; contentSize: {414, 0}; adjustedContentInset: {0, 0, 0, 0}; dataSource: <TtGC5UIKit29UITableViewDiffableDataSourceOC11FXTMproject26CurrencyListViewControllerP10$107a9eb7c7SectionSS: 0x600002960ca0>>

1

1 Answers

1
votes

First of all there is a big design mistake in your code.

With UITableViewDiffableDataSource stop thinking in index paths and data source arrays. Instead think in datasource items.

In setupDataSource you get the model item of the row always from the data source array currencyPairsArray regardless whether you are going to display the filtered data or not. Forget currencyPairsArray and the index path. Take advantage of the third parameter in the closure which represents the item.

func setupDataSource() {
    dataSource = UITableViewDiffableDataSource<Section, String>(tableView: tableView, cellProvider: { [weak self] (tableView, _, pair) ->  UITableViewCell?  in
        let cell = tableView.dequeueReusableCell(withIdentifier: CurrencyPairCell.reuseIdentifier, for: indexPath) as! CurrencyPairCell
        cell.delegate = self
        cell.currencyPairLabel.text = pair.formattedPair()
        cell.currencyPair = pair
        return cell
    })
}

To get rid of the warning perform the first reload of the data without animation. Add a boolean parameter to performSearch. And rather than checking for nil check for empty string

func performSearch(with filter: String, animatingDifferences: Bool = true) {

    var snapshot = NSDiffableDataSourceSnapshot<Section, String>()

    let pairs : [String]
    if filter.isEmpty {
        pairs = currencyPairsArray.sorted()
    } else {
        pairs = currencyPairsArray.filter {$0.contains(filter)}           
    }
    snapshot.appendSections([.main])
    snapshot.appendItems(pairs, toSection: .main)
    dataSource.apply(snapshot, animatingDifferences: animatingDifferences)

}

And never call tableView.reloadData() when using UITableViewDiffableDataSource which is most likely the reason of your issue.

Replace

    performSearch(with: nil)


    fetcher.fetchCurrencyPairs { [weak self] pairsArray in
        self?.currencyPairsArray.append(contentsOf: pairsArray)
        DispatchQueue.main.async {
            self?.tableView.reloadData()
        }
    }

with

    fetcher.fetchCurrencyPairs { [weak self] pairsArray in
        self?.currencyPairsArray.append(contentsOf: pairsArray)
        DispatchQueue.main.async {
            self?.performSearch(with: "", animatingDifferences: false)
        }
    }